线程创建方式
线程创建方式,划分为三种,Thread创建,Runnable创建,以及futureTask整合Callable来实现访问,代码如下:
package com.bo.threadstudy.two;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程测试类
* 使用JMH来完成测试功能
*/
@Slf4j
//查看平均耗时
@BenchmarkMode(Mode.AverageTime)
//执行次数,设置为5
@Measurement(iterations = 5)
//预热次数,设置为3
@Warmup(iterations = 3)
public class CreateThreadTest {
/**
* thread创建线程的方式
*
*/
@Test
@Benchmark
public void createThreadTest(){
Thread thread = new Thread("线程创建"){
@Override
public void run() {
log.debug("执行线程");
}
};
thread.start();
log.debug("执行主程");
}
/**
* 使用Runable的方式来创建对象,这里采用了lambda表达式来解决
*/
@Test
@Benchmark
public void createRunnableTest(){
new Thread(() -> log.debug("使用runnable来创建线程"), "runnable创建").start();
log.debug("主程游走");
}
/**
* 使用futureTask来创建线程,与runAble不同的,可以再当前线程中返回结果来再当前线程中进行调用
* 这种不常见,但是调用多线程的时候,这种按道理应该用的比较多点,因为涉及到使用返回结果来实现线程之间的通信
*/
@Test
public void createFutureTaskTest(){
FutureTask<String> futureTask = new FutureTask<>(() -> {
log.debug("测试call方法正在走");
return "call方法返回结果";
});
Thread thread = new Thread(futureTask, "futureTask创建线程");
thread.start();
log.debug("主程正在执行");
String s = null;
try {
s = futureTask.get();
log.debug("主程获取到结果");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
log.debug("futureTask获取到的结果为"+s);
}
}
简要对比下三种创建的方式。
以Thread创建线程,采用了继承的方式来重写了Thread类之种的run方法,相当于将线程与任务写到了一块。因为java底层类继承仅支持单继承,所以这种方案会导致耦合性较高,对于业务后续扩展并不友好。
以Runnable整合Thread来创建线程,Runnable相当于任务对象,Thread相当于线程对象,这种方式将线程对象与任务这种行为给划分开来,实现了解耦,保证了系统的后期扩展。
以FutureTask结合Callable来整合到Thread中来创建线程,这种方案与Runnable方式类似,只不过我们可以使用FutureTask的get()方法来得到Callable之中的返回结果,从一定程度上保证了线程通信。(类似于线程内部的join方法,等线程执行完成后再执行当前线程,不过这个没有返回结果)。
源码
run方法解析
简单看一下源码,难的我也捋不明白。Thread类下的run方法。
Run方法中依赖于target中的run方法,那么这个target对象是什么,
这个target对象其实就是一个Runable对象,也就是我们第二种创建线程的方式。试想一下,重写
相当于在原有代码上进行改动,而使用Runable则相当于另外定义了接口,在接口中实现该功能。这种方式的实现模式是策略模式。
Runable方法就不介绍了,一个接口,就是为了实现这个run方法来创建的接口。
那么FutureTask结合Callable的实现,底层调用的也是这个run方法,那么FutureTask类与Runnable类是否有一定的联系。
源码种,FutureTask类是实现了Runable类,并整合了泛型来进行操作。而FutureTask中的run方法代码如下:
底层调用的其实是callable中传入的call方法,根据内部实际实现的call方法来执行操作。
所以Thread类中的run方法根据不同入参传入时执行run方法的操作如上述流程。
FutureTask的get方法
我刚才说了,get方法拥有阻塞当前线程的作用,那么是怎么阻塞的。
在这里写了个死循环(自旋方式,也就是CAS),只有state这个状态值满足大于COMPLETING时,才可以跳出这个循环。
那么这个状态在哪里改动呢,既然要当前线程执行完后,get方法才能得到返回结果,那么去run方法接着看看。
刚才在run方法中,我们看到内部有一个set方法。具体源码如下:
这个方法底层是一个native方法,是在这一步,对state状态进行偏移,然后转化为NORMAL状态。从而让get方法执行结束。
中间涉及的state,aqs这个后续随着深入慢点了解,先不着急。
发现一个问题,在测试类中使用Thread.sleep()方法时。设置睡眠时间一长,会导致sleep后续的程序全部不执行,这个得看看。