Java提供了三种创建线程的方法:
- 重写
Thread类的run(); - 通过实现
Runnable接口; - 通过
Callable和FutureTask创建线程。
通过 Thread 类创建
在Java中,每一个线程实际的创建和运行都是通过Thread类。Thread类的run()方法就是当前线程的入口点(入口方法)。
在Java中,在运行
main()方法时,实际上是创建了一个名为main的线程。而main()方法则是当前应用程序main线程的入口点(入口方法)。
直接通过Thread类的无参构造函数创建Thread对象,创建出来的线程并没有任何实际的作用。也就是说它的run()方法并没有执行任何实际的任务。
通过Thread类直接创建线程,需要重写Thread的run()方法。重写方法有两种方式:
-
通过匿名内部类重写
run()方法:1@Slf4j 2public class CreateThread { 3 public static void main(String[] args) { 4 // 匿名内部类创建线程 5 Thread thread = new Thread() { 6 @Override 7 public void run() { 8 log.debug("running..."); 9 } 10 }; 11 12 thread.start(); // 运行线程 13 14 log.debug("running..."); 15}Thread类的start()方法可以用于启动线程。当线程被创建之后,需要手动执行start()方法,线程才会被真正启动。需要注意的是,每个
thread对象的start()方法仅能被执行一次。如果start()方法被多次执行,start()会抛出一个IllegalThreadStateException异常。输出如下:
116:47:40.097 [main] DEBUG CreateThread - running... 216:47:40.097 [Thread-0] DEBUG CreateThread - running... -
通过继承重写
run()方法:1@Slf4j 2class MyThread extends Thread { 3 @Override 4 public void run() { 5 log.debug("running..."); 6 } 7 8 public static void main(String[] args) { 9 new MyThread("my-thread").start(); 10 log.debug("running..."); 11 } 12}输出如下:
116:47:40.097 [main] DEBUG MyThread - running... 216:47:40.097 [Thread-0] DEBUG MyThread - running...
PS:
为了更好地区分各线程,可以引入
logback:1<dependency> 2 <groupId>ch.qos.logback</groupId> 3 <artifactId>logback-classic</artifactId> 4 <version>1.2.3</version> 5</dependency>接着在项目的
resources的目录下添加一个logback.xml:1<?xml version="1.0" encoding="UTF-8"?> 2<configuration 3 xmlns="http://ch.qos.logback/xml/ns/logback" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd"> 6 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 7 <encoder> 8 <pattern>%date{HH:mm:ss} [%t] %logger - %m%n</pattern> 9 </encoder> 10 </appender> 11 <logger name="c" level="debug" additivity="false"> 12 <appender-ref ref="STDOUT"/> 13 </logger> 14 <root level="ERROR"> 15 <appender-ref ref="STDOUT"/> 16 </root> 17</configuration>
线程名称
通过Thread创建线程时,可以为线程设置名称,设置线程的名称有两种方式:
-
通过构造函数:
1new Thread("my-thread") { // 设置线程名称 2 @Override 3 public void run() { 4 /* ... */ 5 } 6}.start(); -
通过
setName()方法:1Thread thread = new Thread() { 2 @Override 3 public void run() { 4 /* ... */ 5 } 6}; 7thread.setName("my-thread"); // 设置线程名称 8thread.start();
通过 Runnable 创建
Runnable是一个接口类,它只包含了run()这一个接口:
1@FunctionalInterface
2public interface Runnable {
3 public abstract void run();
4}
使用Runnable创建Thread,首先需要实现Runnable接口,然后再将实现的Runnable对象传递给Thread的构造函数进行实例化。实现Runnable的方式如下几种:
-
通过匿名内部类实现
run()方法。 -
通过Lambda表达式直接实现
run()方法。由于
Runnable中有且仅有一个接口run(),并且Runnable被@FunctionalInterface注解标注(标注了@FunctionalInterface的接口类可以直接使用Lambda来实现)。所以Runnable可以直接通过Lambda表达式来实现。示例如下:
1// 实现 Runnable 2Runnable runnable = () -> log.debug("running..."); 3// 将 Runnable 传递给 Thread 的构造函数 4new Thread(runnable).start(); -
通过创建新的类来实现
Runnable接口。
当使用
Runnable来实例化Thread时,可以在实例化Thread的同时传递一个String类型的参数来指定Thread的名称。例如:1new Thread(runnable, "my-thread").start();
使用Runnable实例化Thread的好处是,可以实现“线程”(Thread)和“任务”(要执行的代码,即Runnable)的分离。
Runnable 和 Thread
实际上,Thread实现了Runnable接口:
1public class Thread implements Runnable {
2 private Runnable target;
3
4 public Thread() {
5 // 初始化
6 init(null, null, "Thread-" + nextThreadNum(), 0);
7 // 此时 target 为 null, run() 方法没有实际的执行代码
8 }
9
10 public Thread(Runnable target) {
11 // 初始化
12 init(null, target, "Thread-" + nextThreadNum(), 0);
13 // init() 中会将 target 赋给成员变量, 也就是 this.target = target (下同)
14 }
15
16 public Thread(Runnable target, String name) {
17 // 初始化
18 init(null, target, name, 0);
19 }
20
21 @Override
22 public void run() {
23 if (target != null) {
24 target.run();
25 }
26 }
27
28 /* 省略其它... */
29}
在Thread中
通过 Callable 和 FutureTask 创建
Callable是一个接口类,它和Runnable一样只包含了一个方法,并且都被@FunctionalInterface所修饰。只不过Callable需要实现的不是run()接口,而是call()接口。Callable的定义如下:
1@FunctionalInterface
2public interface Callable<V> {
3 V call() throws Exception;
4}
可以看到Callable需要指定一个泛型V,而这个泛型V是作为call()接口的返回值。
Callable和Runnable一样可以使用三种方式实现(此处略)。与Runnable不同的是,Callable不是作为Thread构造方法的参数传入,而是作为FutureTask的参数传入。
通过Callable和FutureTask创建Thread通常包含以下步骤:
- 实现
Callable; - 通过
Callable构造FutureTask; - 通过
FutureTask构造Thread。
由于Callable被@FunctionalInterface所修饰,所以可以使用Lambda简化代码。代码示例如下:
1FutureTask<Integer> task = new FutureTask<>(() -> {
2 log.debug("running...");
3 Thread.sleep(1000);
4 return 100;
5});
6
7new Thread(task).start();
8
9// 获取线程执行结果, 等待返回结果 (阻塞)
10log.debug("{}", task.get());
如上所示,通过Callable和FutureTask创建Thread的好处就是,可以使用FutureTask对象来获取call()方法的执行结果。
FutureTask同样有run()方法。因为FutureTask实现了RunnableFuture接口:1public class FutureTask<V> implements RunnableFuture<V> { 2 /* ... */ 3}而
RunnableFuture继承了Runnable接口。所以FutureTask是被作为Runnable的一个实现类被传入Thread的构造函数中。
评论