线程的启动和运行

方法一:使用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之间一个非常重要的搭桥角色。

关系图如下:

可以看出,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变量,这才会对原值产生影响。

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状态。

就绪态和运行态都是操作系统的线程状态。在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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/Thread.java:834)

Re

《Java高并发核心编程》

创建线程的4种方法 and 线程的生命周期的更多相关文章

  1. 27 多线程(一)——创建进程的三种方法、线程锁(同步synchornized与lock)

    线程的流程 线程的创建 有三种方法,重点掌握前两种: 继承Thread类 实现Runnable接口(推荐使用:避免单继承的局限性) 实现Callable接口 根据java的思想,要少用继承,多用实现. ...

  2. java创建线程的三种方法

    这里不会贴代码,只是将创建线程的三种方法做个笼统的介绍,再根据源码添加上自己的分析. 通过三种方法可以创建java线程: 1.继承Thread类. 2.实现Runnable接口. 3.实现Callab ...

  3. 进程和创建线程的两种方法(threading.Thread)

    进程 如QQ 要以一个整体的形式暴露给操作系统管理,里面包含对各种资源的调用,内存的管理, 网络接口的调用等,进程就是各种资源管理的集合 线程:是操作系统最小的调度单位,是一串指令的结合 进程 要操作 ...

  4. 《Java多线程面试题》系列-创建线程的三种方法及其区别

    1. 创建线程的三种方法及其区别 1.1 继承Thread类 首先,定义Thread类的子类并重写run()方法: package com.zwwhnly.springbootaction.javab ...

  5. (转)Java结束线程的三种方法

    背景:面试过程中问到结束线程的方法和线程池shutdown shutdownnow区别以及底层的实现,当时答的并不好. Java结束线程的三种方法 线程属于一次性消耗品,在执行完run()方法之后线程 ...

  6. Java结束线程的三种方法(爱奇艺面试)

    线程属于一次性消耗品,在执行完run()方法之后线程便会正常结束了,线程结束后便会销毁,不能再次start,只能重新建立新的线程对象,但有时run()方法是永远不会结束的.例如在程序中使用线程进行So ...

  7. Java新建线程的3种方法

    Java新建线程的3种方法 =================== Java创建线程有3种方法:(1)继承Thread;(2)实现Runnable接口:(3)实现Callable接口; 由于Java只 ...

  8. Java结束线程的三种方法

    线程属于一次性消耗品,在执行完run()方法之后线程便会正常结束了,线程结束后便会销毁,不能再次start,只能重新建立新的线程对象,但有时run()方法是永远不会结束的.例如在程序中使用线程进行So ...

  9. Solr创建Core的两种方法

    创建Core的两种方法: 第一种方法: 1.打开dos命令窗口,切换目录到${solr.home}\bin,然后输入:solr create -c corename之后回车: 2.打开solr安装文件 ...

随机推荐

  1. opencv入门系列教学(七)改变颜色空间、提取彩色对象

    ​ 0.序言 之前的博客里我们介绍了opencv在图像上的基本操作,下面我们来进行稍微深入一点的介绍,从这里开始我们可以发现opencv库能给我们带来的更多更有趣的功能.从现在开始,我们将逐步深入了解 ...

  2. Android App性能测试之adb命令

    本篇文章总结了Android App性能测试过程中常用的adb命令.通过这些adb命令,可以查看App的性能数据,为评判性能好坏作参考. CPU相关 显示占用CPU最大的5个应用 adb shell ...

  3. 剑指 Offer 31. 栈的压入、弹出序列

    剑指 Offer 31. 栈的压入.弹出序列 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如,序列 {1,2,3,4,5} 是某 ...

  4. Docker私有镜像仓库Harbor

    一.安装Harbor(离线安装包的方式安装) 1.解压离线包 2.进入harbor目录中编辑harbor.yml 3.安装docker-compose yum -y install docker-co ...

  5. 痞子衡嵌入式:MCUXpresso IDE下将应用程序RW段分散链接的几种方法

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是MCUXpresso IDE下将应用程序RW段分散链接的几种方法. 早期的 MCU 芯片,一般都会嵌入内部 Flash 和 RAM,并且 ...

  6. Python - 面向对象编程 - __new()__ 和单例模式 

    单例模式 这是一种设计模式 设计模式是前任工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案 使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性 单 ...

  7. WEB漏洞——RCE

    RCE(remote command/code execute)远程命令/代码执行漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统. RCE漏洞 应用程序有时需要调用一 ...

  8. P1721 [NOI2016] 国王饮水记 题解

    蒟蒻的第一篇黑题题解,求过. 题目链接 题意描述 这道题用简洁的话来说,就是: 给你 \(n\) 个数字,你可以让取其中任意若干个数字,每次操作,都会使所有取的数字变为取的数字的平均数,并且你最多只能 ...

  9. VUE003. 解决data中使用vue-i18n不更新视图问题(computed属性)

    案例 在国际化开发中,有一部分需要国际化的文字是由数据驱动的储存在data中,然而VUE的data存在很多无法实时更新视图的问题,比如v-for循环的标签,当数据层次过深,通过源数据数组的索引改变它的 ...

  10. 第25篇-虚拟机对象操作指令之putstatic

    之前已经介绍了getstatic与getfield指令的汇编代码执行逻辑,这一篇介绍putstatic指令的执行逻辑,putfield将不再介绍,大家可以自己去研究,相信大家有这个实力. putsta ...