Java并发编程学习一:线程的概念以及使用

该篇文章做为本身并发学习的一个开始,首先介绍一下线程的概念以及使用。html

讨论基于单核cpu进行java

线程的意义

要了解线程的意义,首先先介绍一下进程,什么是进程?进程概念以下(摘自百度百科):算法

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。多线程

广义定义:进程是一个具备必定独立功能的程序关于某个数据集合的一次运行活动。它是操做系统动态执行的基本单元,在传统的操做系统中,进程既是基本的分配单元,也是基本的执行单元。并发

简单的理解就是一个Andorid的App(内部不引入多进程状况下)应用程序表明的一个进程,该进程在系统中拥有本身的内存空间,全部进程互相独立,互不影响。经过进程这个抽象的概念,使得操做系统中运行不一样的应用程序成为了可能。进程有三个状态:oracle

  • 运行态/执行态(Running):当一个进程在cpu上运行时,则称该进程处于运行状态。
  • 就绪态(Ready):一个进程得到了除处理机外的一切所需资源,一旦获得cpu便可运行,则称此进程处于就绪状态。
  • 阻塞态(Blocked):一个进程正在等待某一事件发生(例如IO操做)而暂时中止运行称该进程处于阻塞状态。

进程的状态在上面三个状态中转换,假设系统中总共有3个进程针对单核cpu的状况,那么一个时间片内只会有一个进程处于运行态,其余的进程分别处于就绪态和阻塞态,具体的转换过程以下图所示:ide

进程的概念大致介绍到这,那么既然提出来了进程,为何又要提出线程的概念呢?试想但一个进程中存在多任务,好比在进程中输入过程当中须要读取历史记录,同时还要响应用户的输入事件,那么在进程中只能串行的进行,如何进行并行就是线程出现的意义了。学习

线程的概念以下(摘自百度百科):测试

线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程本身不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的所有资源。this

线程是CPU调度的基本单位,在Java中一个进程至少有一个线程,多线程的概念其实是对于系统时间片抢占的表现。对于单核cpu而言,同一时间内仍是只能容许一个线程的执行,经过轮流切换使用时间片,达到并发的概念。讲到并发,并行跟并发的概念一块儿记录一下,用一张图表示一下两则的区别:

Erlang 之父 Joe Armstrong提供的一张解释图,能够主观理解一下。并发指的是一个处理器同时处理多个任务,这种处理方式经过轮流得到时间片方式进行,而并行是并发的子集,并行指的是多个处理器或者是多核的处理器同时处理多个不一样的任务。

Java中的线程

线程在Java中的实现经过以下三种方式:

  • 继承Thread,重写run()方法。
  • 实现Runnable接口。
  • 经过Callable和Future建立。

建立简单调用Demo以下:

class ThreadTest {

    private Runnable mRunnable = () -> System.out.println("from runnable");

    private class TestThread extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println("from Thread");
        }
    }

    private Callable<Void> mCallable = () -> {
        System.out.println("from Callable");
        return null;
    };

    void testThread() {
        new TestThread().run();
        new Thread(mRunnable).run();
        FutureTask<Void> task = new FutureTask<Void>(mCallable);
        new Thread(task).run();
    }
}
-----
输出结果:
from Thread
from runnable
from Callable

上面经过thread.start()调用启动了线程,线程在执行过程当中是有生命周期的从出生到死亡,其生命周期以下图片(来自菜鸟教程)所示:

  • 新建状态:即对咱们在new一个Thread的时候,此时初始化相关资源,等待start()的调用。
  • 就绪状态:在调用start()方法后,线程就如就绪状态,等待cpu进行调用。
  • 运行状态:在run()方法被调用时说明处于运行状态,此时线程得到cpu资源进行工做。
  • 阻塞状态(BLOCKED,WAITING,TIME_WAITING):在运行过程当中线程让出cpu资源,而且从新进入就绪状态等待cpu调用。
  • 死亡状态:线程执行完毕。

关于各个方法对应生命周期的图以下:

接下来看下线程中的一些重要方法:

注:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,再也不介绍。由于有死锁倾向。

Thread.sleep(long millis)

Thread.sleep(long millis)使线程从运行状态转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。经过该方法可以使得当前线程暂停执行,让出cpu资源给其余的须要的使用,这时候状态为BLOCK状态,必定时间后进入就绪状态,准备从新获取cpu。**须要注意的是若是当前线程进入了同步锁,sleep方法并不会释放锁,即便当前线程使用sleep方法让出了CPU,但其余被同步锁挡住了的线程也没法获得执行。**在sleep()休眠时间事后,该线程也不必定会立刻执行,这是由于其它线程可能正在运行并且没有被调度为放弃执行,除非此线程具备更高的优先级。

这里须要区分sleep和wait的区别,wait和notify方法跟sychronized关键字一块儿配套使用,wait()方法在进入等待状态的时候,这个时候会让度出cpu资源让其余线程使用,与sleep()不一样的是,这个时候wait()方法是不占有对应的锁的.

首先看下不使用同步锁时候的代码:

void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        new Thread(mRunnable).start();
    }
	//输出结果
2018-11-09 18:52:26 PM : from Thread start run
2018-11-09 18:52:26 PM : from runnable start run
2018-11-09 18:52:26 PM : from runnable start end
2018-11-09 18:52:29 PM : from Thread end

当咱们当用sleep()方法时候确实是会释放出cpu资源的。那么继续确认一下使用同步锁的状况:

private class TestThread extends Thread {
        @Override
        public void run() {
            super.run();
            synchronized (ThreadTest.class) {
                println("from Thread start run");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                println("from Thread end");
            }

        }
    }

    void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        new Thread(mRunnable).start();
    }

    private Runnable mRunnable = () -> {
        synchronized (ThreadTest.class) {
            println("from runnable start run");
            println("from runnable start end");
        }
    };
//输出结果
2018-11-09 18:54:46 PM : from Thread start run
2018-11-09 18:54:49 PM : from Thread end
2018-11-09 18:54:49 PM : from runnable start run
2018-11-09 18:54:49 PM : from runnable start end

上面的输出结果验证了咱们的结论的正确性。

####Thread.join(long millis)

join()方法容许一个线程A等待另外一个线程B执行完毕后再执行,也就是说具体使用以下所示:

void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main thread executed");
    }
	
----
//输出结果
2018-11-09 18:30:14 PM : from Thread start run
2018-11-09 18:30:17 PM : from Thread end
main thread executed

能够看到主线程会等待TestThread线程执行完毕后在打印出log。主线程在调用thread.join()方法后进入了阻塞状态,等到thread执行完毕后恢复就绪状态,等待cpu从新调用。

须要注意的是join()方法实现是经过Object.wait()方法。 当main线程调用thread.join时候,main线程会得到线程对象thread的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,好比退出后。这就意味着main 线程调用thread.join时,必须可以拿到线程thread对象的锁,详细可查阅该篇文章

Thread.yield()

该方法的表示该线程让出cpu资源,**从运行态直接转换为就绪态。**通俗的将就是说当一个线程A使用了这个yield()方法以后,它就会把本身CPU执行的时间让掉,cpu经过调度算法从新让A或者其它的线程运行。

示例Demo以下:

private class TestThread extends Thread {
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 10; i++) {
                println("TestThread print" + (i + 1) + " start");
                if (i == 3) {
                    yield();
                }
            }
        }
    }

    void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        new Thread(mRunnable).start();
    }

    private Runnable mRunnable = () -> {
        for (int i = 0; i < 10; i++) {
            println("Runnable print" + (i + 1) + " start");
        }
    };
	//输出结果
2018-11-09 19:36:29 PM : Runnable print1 start
2018-11-09 19:36:29 PM : TestThread print1 start
2018-11-09 19:36:29 PM : Runnable print2 start
2018-11-09 19:36:29 PM : TestThread print2 start
2018-11-09 19:36:29 PM : Runnable print3 start
2018-11-09 19:36:29 PM : TestThread print3 start
2018-11-09 19:36:29 PM : Runnable print4 start
2018-11-09 19:36:29 PM : TestThread print4 start
2018-11-09 19:36:29 PM : Runnable print5 start
2018-11-09 19:36:29 PM : Runnable print6 start
2018-11-09 19:36:29 PM : Runnable print7 start
2018-11-09 19:36:29 PM : Runnable print8 start
2018-11-09 19:36:29 PM : Runnable print9 start
2018-11-09 19:36:29 PM : Runnable print10 start
2018-11-09 19:36:29 PM : TestThread print5 start
2018-11-09 19:36:29 PM : TestThread print6 start
2018-11-09 19:36:29 PM : TestThread print7 start
2018-11-09 19:36:29 PM : TestThread print8 start
2018-11-09 19:36:29 PM : TestThread print9 start
2018-11-09 19:36:29 PM : TestThread print10 start

当TestThread中的i等于3的时候能够看见确实TestThread让出cpu让Runnable中的多执行了一会。

Thread各类interrupt方法

在使用线程的时候,JDK为咱们提供了中断线程的操做:interrupt方法,这里须要注意,经过interrupt方法只是通知线程A应该中断了,而中断与否由线程A自主控制。关于Thread中的各类interrupt主要有三个,接下来依次看下

//静态方法
 public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

 /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

该方法调用当前线程的isInterrupted(boolean ClearInterrupted)方法,isInterrupted(boolean ClearInterrupted)方法可以获得当前线程的状态,若是传递的是true,那么在返回当前线程状态后,它会把当前线程的interrupt状态“复位”,假设当前线程的isInterrupt状态为true,它会返回true,但事后isInterrupt的状态会复位为false。

//实例方法
 public boolean isInterrupted() {
        return isInterrupted(false);
    }

该方法为实例对象调用的方法,与静态interrupted()方法相同,惟一区别是参数传递的是false,即表示直接返回当前线程的状态

public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

interrupt()方法为线程中断的方法,经过该方法可以通知对应的线程能够进入中断的状态了。若是线程在调用wait()方法,join()方法,sleep()方法后阻塞,这时候进行调用interrupt()方法,那么线程会抛出InterruptedException异常;若是线程NIO操做中阻塞,则会抛出ClosedByInterruptException异常(因为作客户端,这块不是特别了解,写错了麻烦纠正一下)。特别注意一点,调用该方法的必定是阻塞的线程来调用。

当咱们在线程内部中有使用可以抛出InterruptedException异常方法时,官方建议咱们经过主动调用thread.interrupt()方法进行中断线程的请求;若是线程中没有,则使用Thread.interrupted()方法以及thread.isInterrupted()方法进行判断进行中断请求。复杂的时候能够混合使用来进行中断请求的操做。

接下来就是写代码验证的时候了,首先咱们测试一下调用interrupt()`方法:

public static void main(String[] args)  {
        TestThread testThread = new TestThread();
        testThread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        testThread.interrupt();
    }
public static class TestThread extends Thread{

        @Override
        public void run() {
            super.run();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println("interrupt occur");
            }
        }
    }
--------
//输出结果
interrupt occur

能够看到,在主线程调用testThread.interrupt()方法后,TestThread抛出了InterruptedException异常,再试试看join()方法:

public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        TestThread1 testThread1 = new TestThread1(testThread);
        testThread.start();
        Thread.sleep(600);
        testThread1.start();
        Thread.sleep(600);
        testThread1.interrupt();
    }
    public static class TestThread1 extends Thread {
        private TestThread mThread;

        public TestThread1(TestThread thread) {
            mThread=thread;
        }
        @Override
        public void run() {
            super.run();
            try {
                mThread.join();
                System.out.println("executed after  TestThread");
            } catch (InterruptedException e) {
                System.out.println("InterruptedException for TestThread");
            }
            System.out.println("do TestThread1 job");
        }
    }
    public static class TestThread extends Thread {


        @Override
        public void run() {
            super.run();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
            }
        }
    }
	//输出结果
	InterruptedException for TestThread
	do TestThread1 job

能够看到,在testThread1正处于阻塞的时候,咱们调用testThread1.interrupt()后,抛出了InterruptedException 异常,TestThread1没有等到TestThread执行完毕就直接走本身的任务了。

Ok,接下来再看看interrupted()以及isInterrupted()的验证过程,这里写个死循环来模拟耗时的操做:

public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        testThread.start();
        Thread.sleep(3000);
        System.out.println("execute testThread.interrupt method");
        testThread.interrupt();
    }

    public static class TestThread extends Thread {


        @Override
        public void run() {
            super.run();
            int i=1;
            while (!isInterrupted()) {
                i=2;
            }
            System.out.println("method for is  isInterrupted's value="+isInterrupted());
            System.out.println("method for is  isInterrupted's value2="+isInterrupted());
        }
    }
//结果
execute testThread.interrupt method
method for is  isInterrupted's value=true
method for is  isInterrupted's value2=true

上面的结果能够看到isInterrupted()方法执行得到到的一直是true,再看看interrupted()方法:

public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        testThread.start();
        Thread.sleep(3000);
        System.out.println("execute testThread.interrupt method");
        testThread.interrupt();
    }

    public static class TestThread extends Thread {


        @Override
        public void run() {
            super.run();
            int i=1;
            while (!isInterrupted()) {
                i=2;
            }
            System.out.println("method for is  interrupted's value="+Thread.interrupted());
            System.out.println("method for is  interrupted's value2="+Thread.interrupted());
        }
    }
//结果
execute testThread.interrupt method
method for is  interrupted's value=true
method for is  interrupted's value2=false

这里也符合咱们上面获得的结论,在调用Thread.interrupted()第一次时候返回了true,java内部把标志位清除为了false,第二次再去获取就变成了false。


参考文章

相关文章
相关标签/搜索