创建线程的4种方法 and 线程的生命周期

线程的启动和运行

方法一:使用start()方法:用来启动一个线程,当调用start方法后,JVM会开启一个新线程执行用户定义的线程代码逻辑。

方法二:使用run()方法:作为线程代码逻辑的入口方法。run方法不是由用户程序来调用的,当调用start方法启动一个线程之后,只要线程获得了CPU执行时间,便进入run方法去执行具体的用户线程代码。

start方法用于启动线程,run方法是用户逻辑代码执行入口。

1.创建一个空线程

main {
    Thread thread = new Thread();
    thread.start();
}

程序调用start方法启动新线程的执行。新线程的执行会调用Thread的run方法,该方法是业务代码的入口。查看一下Thread类的源码,run方法的具体代码如下:

public void run() {
    if(this.target != null) {
        this.target.run();
    }
}

这里的target属性是Thread类的一个实例属性,该属性非常重要,后面会讲到。在Thread类中,target属性默认为空。在这个例子中,thread属性默认为null。所以在thread线程执行时,其run方法其实什么也没做,线程就执行完了。

2.继承Thread类创建线程

new Thread(() -> {
            System.out.println(1);
        }).start();

3.实现Runnable接口创建线程

class TestMain implements Runnable {
    main {
        new Thread(TestMain::new).start();
    }
     
    @Override
    public void run() {
        System.out.println(12);
    }
}

4.使用Callable和FutureTask创建线程

前面的Thread和Runnable都不能获取异步执行的结果。为了解决这个问题,Java在1.5之后提供了一种新的多线程创建方法:Callable接口和FutureTask类相结合创建线程。

1.Callable接口

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Callable接口是一个泛型接口,也是函数式接口。其唯一的抽象方法call有返回值。

Callable能否和Runnable一样,作为Thread实例的target使用呢?

答案是不可以。因为Thread的target属性类型为Runnable,所以一个在Callable与Thread之间搭桥接线的重要接口即将登场。

2.RunnableFuture接口

这个重要的接口就是RunnableFuture接口,他实现了两个目标,一是可以作为Thread实例的target实例,二是可以获取异步执行的结果。

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

通过源码可以看出:RunnableFuture是通过继承Runnable和Future来实现上面2个目标的。

3.Future接口

Future接口至少提供了三大功能:(1)取消执行中的任务(2)判断任务是否完成(3)获取任务的执行结果

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  • V get():获取异步任务执行的结果,这个方法的调用是阻塞性的。如果异步任务没有执行完成,会一直被阻塞直到任务执行完成。

总体来说,Future是一个对异步任务进行交互、操作的接口。但是Future只是一个接口,它没有办法直接完成对异步任务的操作,JDK提供了一个默认的实现类—–FutureTask。

4.FutureTask类

FutureTask类是Future接口的实现类,提供了对异步任务的操作的具体实现。但是FutureTask类不仅实现了Future接口,还实现了Runnable接口,更精准的说FutureTask类实现了RunnableFuture接口。

前面提到RunnableFuture接口很关键,既可以作为Thread线程实例的target目标,又可以获取并发任务执行的结果,是Thread与Callable之间一个非常重要的搭桥角色。

关系图如下:

image

可以看出,FutureTask既能作为一个Runnable类型的target,又能作为Future异步任务来获取Callable的计算结果。

FutureTask是如何完成多线程的并发执行、任务结果的异步获取呢?他的内部有一个Callable类型的成员:

private Callable<V> callable;

Callable实例属性用来保存并发执行的Callable类型的任务,并且Callable实例属性需要在FutureTask实例构造时初始化。FutureTask实现了Runnable接口,在run方法的实现中会执行Callable的call方法。

FutureTask内部有一个outcome实例属性用于保存执行结果。

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

使用Callable和FutureTask创建线程的具体步骤

(1)创建Callable接口的实现类,并实现call方法,编写好具体逻辑。

(2)使用Callable实现类的实例构造一个FutureTask实例。

(3)使用FutureTask实例作为Thread构造器的target入参,构造新的Thread线程实例

(4)调用Thread实例的start方法启动新线程,内部执行过程:启动Thread实例的run方法并发执行后,会执行FutureTask实例的run方法,最终会并发执行Callable实现类的call方法。

(5)调用FutureTask对象的get方法阻塞的获得结果。

public static void main(String[] args) {
        new Thread(new FutureTask<>(() -> {
            int i = 1;
            return i;
        })).start();
    }

5.线程池创建线程

1.线程池的创建与执行目标提交

创建一个固定3个线程的线程池。

private static ExecutorService pool = Executors.newFixedThreadPool(3);

向ExecutorService线程池提交异步执行target目标任务的常用方法有:

// 方法一:无返回
void execute(Runnable command);
// 返回一个Future实例
<T> Future<T> submit(Callable<T> task);
// 也是返回Future实例
Future<?> submit(Runnable task);

2.线程池使用实战

public class TestMain {

    public static ExecutorService pool = Executors.newFixedThreadPool(3);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        pool.execute(() -> {
            int i = 1;
        });

        final Future<?> submit = pool.submit(() -> {
            int i = 1;
            return i;
        });
        Integer o = (Integer) submit.get();
        System.out.println("o: " + o);


        AtomicInteger ans = new AtomicInteger();
        final Future<AtomicInteger> submit1 = pool.submit(new FutureTask<>(() -> {
            int i = 1;
            ans.set(i);
            return ans;
        }), ans);
        System.out.println("ans: " + submit1.get());
        pool.shutdown();
    }

Lambda中不允许使用局部变量:因为使用的是局部变量的副本,对局部变量本身不起作用,所以不能使用。但是可以使用引用类型的变量,比如原子类的ans变量,这才会对原值产生影响。

image

execute与submit区别:

(1)接收参数不一样。

(2)submit有返回值,execute没有返回值。

说明:实际生产环境禁止使用Executors创建线程池。

线程的核心原理

1.线程的生命周期

public static enum State {
    NEW, 新建
    RUNNABLE, 可执行
    BLOCKED, 阻塞
    WATTING, 等待
    TIMED_WAITING, 超时等待
    TERMINATED; 终止
}

在定义的6种状态中,有4种比较常见的状态,他们是:NEW、RUNNABLE、TERMINATED、TIMED_WAITING。

  • NEW状态:创建成功但是没有调用start方法启动的线程实例都处于NEW状态。

当然,并不是线程实例的start方法一调用,其状态就从NEW到RUNNABLE,此时并不意味着线程立即获取CPU时间片并且立即执行。、

  • RUNNABLE状态:前面说到,当调用了线程实例的start方法后,下一步如果线程获取CPU时间片开始执行,JVM将异步调用线程的run方法执行其业务代码。那么在run方法被调用之前,JVM在做什么呢?

JVM的幕后工作和操作系统的线程调度有关。当Java线程示例的start方法被调用后,操作系统中对应线程并不是运行状态,而是就绪状态,而Java线程没有就绪态。就绪态的意思就是该线程已经满足执行条件,处于等待系统调度的状态,一旦被选中就会获得CPU时间片,这时就变成运行态了。

在操作系统中,处于运行状态的线程在CPU时间片用完后,又回到就绪态,等待CPU的下一次调度。就这样,操作系统线程在就绪态和运行态之间被反复调度,知道线程的代码逻辑完成或者异常终止为止。这时线程进入TERMINATED状态。

image

就绪态和运行态都是操作系统的线程状态。在Java中,没有细分这两种状态,而是将他们二合一,都叫作RUNNABLE状态。这时Java线程状态和操作系统不一样的的地方。

总结:NEW状态的线程实例调用了start方法后,线程的状态变为RUNNABLE。但是线程的run方法不一定会马上执行,需要线程获取了CPU时间片之后才会执行。

  • TERMINATED状态:run方法执行完成之后就变成终止状态了,异常也会。
  • TIME_WAITING状态:处于等待状态,例如Thread.sleep,Objects.wait,Thread.join等。(主动)
  • BLOCKED状态:处于此状态的线程不会占用CPU资源,例如线程等待获取锁,IO阻塞。(被动)

2.线程状态实例

让五个线程处于TIME-WAITING状态,使用Jstack查看。

public static void main(String[] args) throws InterruptedException {

    for (int i = 0; i < 5; i++) {
        new Thread(() -> {
            for (int j = 0; j < 500; j++) {

                try {
                    Thread.sleep(500);
                    System.out.println(j);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }).start();
}
"Thread-0" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624761800 nid=0x2fcc waiting on condition  [0x0000003944bff000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep([email protected]/Native Method)
        at com.test.TestMain.lambda$main$0(TestMain.java:33)
        at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
        at java.lang.Thread.run([email protected]/Thread.java:834)

"Thread-1" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624762000 nid=0x1d74 waiting on condition  [0x0000003944cfe000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep([email protected]/Native Method)
        at com.test.TestMain.lambda$main$0(TestMain.java:33)
        at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
        at java.lang.Thread.run([email protected]/Thread.java:834)

"Thread-2" #16 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624763800 nid=0x3f08 waiting on condition  [0x0000003944dfe000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep([email protected]/Native Method)
        at com.test.TestMain.lambda$main$0(TestMain.java:33)
        at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
        at java.lang.Thread.run([email protected]/Thread.java:834)

"Thread-3" #17 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624765000 nid=0x2b5c waiting on condition  [0x0000003944eff000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep([email protected]/Native Method)
        at com.test.TestMain.lambda$main$0(TestMain.java:33)
        at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
        at java.lang.Thread.run([email protected]/Thread.java:834)

"Thread-4" #18 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624768000 nid=0x52f0 waiting on condition  [0x0000003944ffe000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep([email protected]/Native Method)
        at com.test.TestMain.lambda$main$0(TestMain.java:33)
        at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
        at java.lang.Thread.run([email protected]/Thread.java:834)

Re

《Java高并发核心编程》

Tags: