1.常见方法
- join
作用:当某一线程调用该方法时,程序会等待该线程运行结束再执行后续代码
例如
@Slf4j
public class test1 {
public static void main(String[] args) throws InterruptedException {
Runnable r = () -> {
log.info("begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("end");
};
Thread t = new Thread(r, "t1");
t.start();
t.join();
log.info("main end...");
}
}
- 不加t.join()
16:46:28.691 [t1] INFO com.example.concurent.test.test1 -- begin
16:46:28.691 [main] INFO com.example.concurent.test.test1 -- main end...
16:46:29.695 [t1] INFO com.example.concurent.test.test1 -- end
- 加t.join()
16:50:30.060 [t1] INFO com.example.concurent.test.test1 -- begin
16:50:31.064 [t1] INFO com.example.concurent.test.test1 -- end
16:50:31.064 [main] INFO com.example.concurent.test.test1 -- main end...
等待多个结果
当使用join等待多个线程运行结束时,耗时最长线程的耗时为总耗时。
例如:t1线程睡眠1000ms,t2线程睡眠2000ms,总耗时为2002ms。
@Slf4j
public class test1 {
public static void main(String[] args) throws InterruptedException {
Runnable r1 = () -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};
Runnable r2 = () -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};
Thread t1 = new Thread(r1, "t1");
Thread t2 = new Thread(r2, "t2");
var begin = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
var end = System.currentTimeMillis();
log.info("{}", end - begin);
}
}
结果:
16:54:22.116 [main] INFO com.example.concurent.test.test1 -- 2001
有时效的 join
可以在调用join方法添加时间参数(单位:毫秒),join如果等待时间超过该参数,则不会继续等待,而是执行后续代码。
- interrupt
作用:打断线程
打断sleep的线程:会清空打断状态,并且抛出InterruptedException异常
打断正常的线程:不会清空打断状态
例如,t1是sleep的线程,t2是正常的线程
@Slf4j
public class test1 {
public static void main(String[] args) throws InterruptedException {
Runnable r1 = () -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};
Thread t1 = new Thread(r1, "t1");
t1.start();
TimeUnit.MILLISECONDS.sleep(500);
t1.interrupt();
log.info("{}", t1.isInterrupted());
Runnable r2 = () -> {
while (true) {
var currentThread = Thread.currentThread();
var interrupted = currentThread.isInterrupted();
if (interrupted) {
log.info("{}", interrupted);
break;
}
}
};
Thread t2 = new Thread(r2, "t2");
t2.start();
t2.interrupt();
}
}
结果:
Exception in thread "t1" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
at com.example.concurent.test.test1.lambda$main$0(test1.java:16)
at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.example.concurent.test.test1.lambda$main$0(test1.java:14)
... 1 more
17:17:24.438 [main] INFO com.example.concurent.test.test1 -- false
17:17:24.443 [t2] INFO com.example.concurent.test.test1 -- true
2.两阶段终止模式
使用interrupt方法设置该线程的打断状态,当该线程读取到打断状态为true或者抛出InterruptedException异常(sleep状态被打断)时,再执行具体打断操作,也就是说,该线程不会真正停止,具体怎么停止由该线程说了算。
例如:
@Slf4j
public class test1 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination tpt = new TwoPhaseTermination();
tpt.start();
log.info("tpt start");
Thread.sleep(3500);
tpt.stop();
}
}
@Slf4j
class TwoPhaseTermination {
private Thread monitor;
// 启动监控线程
public void start() {
monitor = new Thread(() -> {
while (true) {
Thread thread = Thread.currentThread();
if (thread.isInterrupted()) {
log.info("料理后事");
break;
}
try {
Thread.sleep(1000);
log.info("执行监控记录");
} catch (InterruptedException e) {
e.printStackTrace();
// 重新设置打断标记
thread.interrupt();
}
}
});
monitor.start();
}
public void stop() {
monitor.interrupt();
}
}
3.守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守 护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
注意:
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等 待它们处理完当前请求