JUC并发编程 -- 创建线程的3种方式详解 & 原理之Thread 与 Runnable 的关系
Posted Z && Y
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC并发编程 -- 创建线程的3种方式详解 & 原理之Thread 与 Runnable 的关系相关的知识,希望对你有一定的参考价值。
准备工作:
- 为了打印更加直观,我这里导入slf4j日志的依赖
pom.xml
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
在resource目录下面放入slf4j配置文件
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration
xmlns="http://ch.qos.logback/xml/ns/logback"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{HH:mm:ss} [%t] %logger - %m%n</pattern>
</encoder>
</appender>
<logger name="c" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<root level="ERROR">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
1. 方法一,直接使用 Thread
代码:
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
log.debug("t1 running");
}
};
// 给线程起名字
t.setName("t1");
// 启动t1线程
t.start();
// 主线程打印消息
log.debug("main running");
}
}
运行结果:
2. 方法二,使用Runnable配合Thread
代码:
import lombok.extern.slf4j.Slf4j;
// 只有以c开始的消息才会打印
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
// r是任务对象
Runnable r = new Runnable() {
@Override
public void run() {
log.debug("t2 running");
}
};
// t是线程对象, 构造方法的第一个参数丢入任务对象, 第二个参数设置线程的名称
Thread t = new Thread(r, "t2");
t.start();
// 主线程打印消息
log.debug("main running");
}
}
结果:
3. Java 8以后可以使用lambda精简代码
Runnable接口里面有一个@FunctionalInterface注解,说明这个接口可以被lamda表达式简化。(注意:@FunctionalInterface存在只有一个方法的接口中)
代码:
import lombok.extern.slf4j.Slf4j;
// 只有以c开始的消息才会打印
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
// 只有一个语句 可以省略{}
Runnable r = () -> log.debug("t1 running");
// t是线程对象, 构造方法的第一个参数丢入任务对象, 第二个参数设置线程的名称
Thread t1 = new Thread(r, "t1");
t1.start();
// Thread构造方法使用lambda表达式简化
new Thread(() -> log.debug("t2 running"), "t2").start();
// 主线程打印消息
log.debug("main running");
}
}
运行结果:
4. 方法三,FutureTask 配合 Thread
FutureTask能够接收Callable类型的参数,用来处理有返回结果的情况
FutureTask:
- 可以看见FutureTask 实现了RunnableFuture这个接口
- 可以看见RunnableFuture这个接口实现了Runnable,所以它可以作为一个任务对象,另外它还实现了Future接口。
- Future接口里面有一个get方法,可以返回任务的执行结果
- 我们去查看Runnable接口的源码,发现它这个run方法是没有返回值的,所以Runable接口并不方便的实现线程之间的通信
Callable:
- Callable接口有一个call方法,并且它可以抛出异常(Runnable的run方法不可以抛出异常)
代码:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
@Slf4j(topic = "c.test3")
public class Test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Integer是FutureTask的返回值类型
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("t1 Running");
// 让当前线程睡眠1s 然后返回520
Thread.sleep(1000);
return 520;
}
});
// 启动线程
new Thread(task, "t1").start();
// 获取FutureTask的返回结果 {}是一个占位符,用来打印后面的结果
log.debug("task返回值: {}", task.get());
}
}
运行结果:
5. 原理之Thread 与 Runnable 的关系
-
我们先来看Thread类的构造方法,把Runnable接口实现类当作参数
-
调用init方法
-
点击init方法,发现又进入了一个重载的init方法
-
再次点击init方法,发现就是把这个Runnable接口实现类赋值给Thread类一个名为target的成员变量。
- 发现最终调用的Thread类的Run方法,所以方法2最终也是调用的Thread类的Run方法
结论:
- Thread类继承了Runnable接口,重写了Run方法。方法一和方法二的本质其实都是走的方法一的run方法
6. 小结
- 方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
- 用 Runnable 更容易与线程池等高级 API 配合
- 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
- 推荐使用Runnable
以上是关于JUC并发编程 -- 创建线程的3种方式详解 & 原理之Thread 与 Runnable 的关系的主要内容,如果未能解决你的问题,请参考以下文章