持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
前期准备
(1)创建Maven项目
(2)在pom.xml中引入如下依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.22</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
(3)在resources目录下创建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>
<!--%date{HH:mm:ss.SSS} %c -->
<pattern>%date{HH:mm:ss.SSS} %c [%t] - %m%n</pattern>
</encoder>
</appender>
<!--<logger name="org.springframework.security.web.FilterChainProxy" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.springframework.security.web.access.intercept.FilterSecurityInterceptor" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>-->
<!--<logger name="org.springframework.security.web" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>-->
<logger name="c" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<root level="ERROR">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
一、直接使用Thread创建线程
语法
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
示例
import lombok.extern.slf4j.Slf4j;
/**
* Created by lilinchao
* Date 2022/10/3
* Description 使用 Thread创建线程
*/
@Slf4j(topic = "c.Test01")
public class Test01 {
public static void main(String[] args) {
// 匿名内部类方式创建 Thread
// 参数"t1":表示该线程名称
Thread t1 = new Thread("t1") {
@Override
// run 方法内实现了要执行的任务
public void run() {
log.debug("t1 running");
}
};
//启动线程
t1.start();
// t1.run();
//在main线程中输出日志到控制台
log.debug("main running");
}
}
运行结果
17:08:35.519 c.Test01 [main] - main running
17:08:35.519 c.Test01 [t1] - t1 running
Java 8 以后可以使用 lambda 精简代码
Thread t1 = new Thread(()->{ log.debug("t1 running"); }, "t1");
//启动线程
t1.start();
start()和run()方法的区别
在使用Thread 创建线程时,不管使用start()方法还是使用run()方法,程序都可以正常运行起来,但是还是有本质区别的
-
start():作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。
通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,run()方法运行结束,此线程随即终止。
-
run():和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程。
如果在主线程中直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径只有一条,程序执行时还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的。
start()方法会启动一个新线程,并在新线程中运行run()方法。而run()则会直接在当前线程中运行该方法,并不会启动一个新线程来运行。
二、使用 Runnable 配合 Thread创建线程(推荐)
把【线程】和【任务】(要执行的代码)分开
- Thread 代表线程
- Runnable 可运行的任务(线程要执行的代码)
语法
Runnable runnable = new Runnable() {
public void run(){
// 要执行的任务
}
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start();
示例
import lombok.extern.slf4j.Slf4j;
/**
* Created by lilinchao
* Date 2022/10/3
* Description 使用 Runnable 配合 Thread
*/
@Slf4j(topic = "c.Test02")
public class Test02 {
public static void main(String[] args) {
// 创建任务对象
Runnable task2 = new Runnable() {
@Override
public void run() {
log.debug("t2 running");
}
};
// 参数1 是任务对象; 参数2 是线程名称
Thread t2 = new Thread(task2, "t2");
t2.start();
log.debug("main running");
}
}
运行结果
17:54:19.618 c.Test01 [main] - main running
17:54:19.619 c.Test01 [t2] - running
使用 lambda 精简代码
因为 Runnable 接口标注了 @FunctionalInterface 这个注解,表示是一个函数式接口,可以使用 lambda 表达式
// 创建任务对象
Runnable task2 = () -> log.debug("t2 running");
// 参数1 是任务对象; 参数2 是线程名称
Thread t2 = new Thread(task2, "t2");
t2.start();
Thread 与 Runnable 的关系
通过源码层面来说明Thread 与 Runnable 的关系
- Runnable源码
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
- Thread源码(部分)
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//...
this.target = target;
//...
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
结论
- 在
Test01类中是把线程和任务合并在了一起,Test02类中是把线程和任务分开了 - 用 Runnable 更容易与线程池等高级API 配合
- 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
三、FutureTask 配合 Thread创建线程
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Created by lilinchao
* Date 2022/10/3
* Description FutureTask 配合 Thread
*/
@Slf4j(topic = "c.Test03")
public class Test03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {
log.debug("t3 running");
//执行完毕后返回结果 100
return 100;
});
// 参数1 是任务对象; 参数2 是线程名称
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get(); //get()方法获取返回结果
log.debug("main running");
log.debug("结果是:{}", result);
}
}
运行结果
18:23:39.883 c.Test03 [t3] - t3 running
18:23:39.886 c.Test03 [main] - main running
18:23:39.886 c.Test03 [main] - 结果是:100
源码分析
- FutureTask源码(部分)
public class FutureTask<V> implements RunnableFuture<V> {
/** The underlying callable; nulled out after running */
private Callable<V> callable;
/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public void run() {
//...
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);
}
}
//...
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
}
- 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;
}
说明
FutureTask内置了一个Callable对象,初始化方法将指定的Callable赋给这个对象。FutureTask实现了Runnable接口,并重写了Run方法,在Run方法中调用了Callable中的call方法,并将返回值赋值给outcome变量- get方法就是取出outcome的值。