
JUC并不是面向初学者的,并且关于JUC线程安全问题,需要接触过JavaWeb开发、JDBC开发、Web服务器、分布式框架才会遇到
所有代码基于jdk1.8,使用了 slf4j 打印日志调试更加方便、lombok简化java bean的编写、Junit测试工具单独测试类
maven父工程依赖:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>程序由指令和数据组成的,但这些指令要运行,数据要读写,就必须将指令加载至Cpu,数据加载至内存。
而在指令运行过程中还需要用到磁盘、网络等设备。
进程就是用来加载指令、管理内存、管理IO的
当一个程序被运行,从磁盘加载这个程序的代码至内存,这就开启了一个进程
而进程就可以视为程序的一个实例。大部分的程序可以同时运行多个实例进程,但有的程序只能启动一个实例进程
好比一个程序,可以打开多个这个程序,例如:记事本,浏览器,视频播放器… 至于有的程序只能启动一个进程,像是大部分音乐软件、杀毒软件都是这样的。
一个进程之内可以分为 一~多 个进程,亦可以将进程看作是由多个线程组成的。
一个线程就是一个指令流,它实际就是将指令流中的一条条指令以一定的顺序交给CPU执行
Java中,线程作为最小调度单位,进程作为资源分配的最小单位。
在windows中,进程相当于一个盒子,里面装满了线程,相当于一个容器
单核的CPU下,线程实际还是串行执行的。
在任务调度器中,将CPU的时间片(时间片最小约:15ms)分给不同的线程使用。
只是由于CPU在线程间切换速度非常块,让我们感觉是同时运行的。

上图即是CPU核心将任务工作在不同线程间反复运行
在有多个核心情况下,每个核心都在同时调用不同线程,这种情况可以称为:并行
而多数情况下都是并发与并行一起出现的。
并发和并行一起出现?

Rob Pike对于并发与并行的描述
举个不恰当的例子:
从方法调用的角度来说:
注意:同步在多线程中还有另外一层意思,是让多个线程步调一致
log.info("start...");
long start = System.currentTimeMillis();
long a = 1,b = 2;
for (long i = 0; i < 900000000; i++) {
a *=b;
}
long end = System.currentTimeMillis();
log.info("now.... ms:"+(end-start));
log.info("continue....");
/////////////////////////////////////////////////////////////////
[main] INFO Sync - start...
[main] INFO Sync - now.... ms:678
[main] INFO Sync - continue....log.info("start...");
new Thread(()->{
long start = System.currentTimeMillis();
long a = 1,b = 2;
for (long i = 0; i < 900000000; i++) {
a *=b;
}
long end = System.currentTimeMillis();
log.info("now.... ms:"+(end-start));
}).start();
log.info("continue....");
//////////////////////////////////////////////////////////
[main] INFO Async - start...
[main] INFO Async - continue....
[Thread-0] INFO Async - now.... ms:685假设一个场景:需要执行3个计算,最后将计算结果汇总
int a = 1;// 执行耗时:10ms
int b = 2;// 执行耗时:5ms
int c = 3;// 执行耗时:2ms
int d = a+b+c; // 执行耗时:1ms 总计耗时:10+5+2 = 17ms如果是串行执行,那么上述代码的执行耗时就没有问题。
而如果是多核并行,那么花费时间只取决于耗时最长的那个计算。即:10ms,最后加上汇总的时间:11ms
请注意:多核并行操作,需要在多核CPU才能提高效率,单核情况下仍然是轮流执行(串行)
MH 是 OpenJDK 团队开发的一款基准测试工具,一般用于代码的性能调优,精度甚至可以达到纳秒级别,适用于 java 以及其他基于 JVM 的语言。
和 Apache JMeter 不同,JMH 测试的对象可以是任一方法,颗粒度更小,而不仅限于rest api。
使用时,我们只需要通过配置告诉 JMH 测试哪些方法以及如何测试,JMH 就可以为我们自动生成基准测试的代码。我们只需要通过配置(主要是注解)告诉 JMH 测试哪些方法以及如何测试,JMH 就可以为我们自动生成基准测试的代码。
要使用 JMH,我们的 JMH 配置项目必须是 maven 项目。在一个 JMH配置项目中,我们可以在pom.xml看到以下配置。JMH 自动生成基准测试代码的本质就是使用 maven 插件的方式,在 package 阶段对配置项目进行解析和包装。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>{version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>${uberjar.name}</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>推荐教程:
Thread t1 = new Thread("Thread-One") {
@Override
public void run() {
log.info("new Thread...");
super.run();
}
};
t1.start();
/////////////////////////////////////////////////////
// [Thread-One] 代表是线程名称
[Thread-One] INFO CaR1T - new Thread...Runnable r1 = new Runnable(){
@Override
public void run() {
log.info("new Runnable...");
}
};
Thread t1 = new Thread(r1);
t1.setName("Thread-Two");
t1.start();
////////////////////////////////////////////////////////////////
[Thread-Two] INFO CaR2T - new Runnable...把【线程】和【任务】(要执行的代码)分开
/**
* lambda表达式简化
*/
@Test
public void test1(){
Runnable r2 = () -> log.info("new Runnable use lambda...");
Thread t1 = new Thread(r2,"Thread-Three");
t1.start();
}
/////////////////////////////////////////
[Thread-Three] INFO CaR2T - new Runnable use lambda...同样的,不论是否是lambda式,一样可以添加代码块
Runnable r3 = () -> {
int i =0;
i++;
log.error("new Runnable use lambda..."+i);
};
new Thread(r3,"Thread-Four").start();
/////////////////////////////////////////////
[Thread-Four] ERROR CaR2T - new Runnable use lambda...1new Thread(r3,"Thread-Four").start();
// 进入Thread 的构造函数
/**
target 参数就是线程需要执行的任务
*/
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
// 随后一直进入 init 方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {}
// 在该方法中有这一段赋值操作
this.target = target; // 线程类,将当前需要执行的任务重新的赋值,并调用run()方法从而运行该任务
// 在run方法中,可以看到就是target这个类被执行了
private Runnable target;
public void run() {
// 当target存在,则优先使用任务并运行
if (target != null) {
target.run();
}
}
可以说,当用户创建了一个线程时,通过Runnable可以自主的选择该线程需要执行的哪个任务 对应第二点和第三点:用 Runnable 更容易与线程池等高级API配合。用Runnable 让任务类脱离了Thread继承体系,更灵活 当创建出一个Runnable对象后,你可以在类中的任何地方使用它,甚至可以通过其他方式传递到别的类中去使用
FutureTask 能够结构 Callable类型 的参数,用来处理有返回结果的情况
FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(()->{
log.info("integerFutureTask....");
return 100;
});
new Thread(integerFutureTask,"t1").start();
Integer result = integerFutureTask.get();// 注意:该方法是阻塞的
log.info("result is:{}", result);
////////////////////////////////////////////////////////
[t1] INFO CaR3T - integerFutureTask....
[main] INFO CaR3T - result is:10上述创建FutureTask类,使用了lambda表达式,其实它被简化的就是Callable接口中的call方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}如果不使用lambda表达式的写法:
new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 0;
}
});new Thread(()->{
while (true){
log.info("线程A......");
}
},"A").start();
new Thread(()->{
while (true){
log.info("线程B......");
}
},"B").start();
/////////////////////////////////////
[A] INFO TR1R - 线程A......
[A] INFO TR1R - 线程A......
[A] INFO TR1R - 线程A......
[A] INFO TR1R - 线程A......
[A] INFO TR1R - 线程A......
[B] INFO TR1R - 线程B......
[B] INFO TR1R - 线程B......
[B] INFO TR1R - 线程B......
[B] INFO TR1R - 线程B......可以看见确实是线程交替运行的
tasklist 查看进程

taskkill /PID <PID> 杀死进程
taskkill /F /PID <PID> 强制杀死进程ps -fe 查看所有进程ps -fT -p <PID\> 查看某个进程(PID)的所有线程kill 杀死进程top 按大写H切换是否显示线程top -H -p <PID\> 查看某个进程(PID)的所有线程
在运行窗口中,直接输入 jconsole 就可以打开监控控制台了
Java Virtual Machine Stacks(Java虚拟机栈)
JVM中,由堆、栈、方法区组成,其中栈内存分配给到的就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存

public static void main(String[] args) {
new Thread(()->{test1(10);},"t1").start();
test1(5);
}
private static void test1(int x){
int b =0;
int c = x+b;
log.info("c:"+c);
}因为以下一些原因导致CPU不再执行当前的线程,转而执行另一个线程的代码
当Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,java中对应的概念就是PC(程序计数器),它的作用是记住下一条jvm指令的执行地址,是线程私有的。

方法名 | 功能说明 | 注意 |
|---|---|---|
start() | 启动一个新线程,在新的线程运行run方法中的代码 | start方法只是让线程进入就绪,里面代码不一定立即运行(Cpu的时间片还没分给它)。每个线程对象的start方法只能调用以此,如果调用了多次会出现IllegalThreadStateException |
run() | 新线程启动后会调用的方法 | 如果在构造Thread对象时传递了Runnable参数,则线程启动后会调用Runnable中的run方法,否则默认不执行任何操作。但可以创建Thread的子类对象,来覆盖默认行为 |
join() | 等待线程运行结束 | |
join(long n) | 呆呆线程运行结束,最多等待n毫秒 | |
getId() | 获得线程长整型的id唯一 | |
getPriority() | 获得线程优先级 | |
setPriority(int) | 修改线程优先级 | java中规定线程优先级时1~10的整数,较大的优先级能提高该线程被CPU调度的几率 |
getState() | 获得线程状态 | java中线程状态时用6个枚举来表示:分别是NewRUNNABLEBLOCKEDWAITINGTIME_WAITINGTERMINATED |
isInterrupted() | 判断是否被打断 | 不会清除打断标记 |
IsAlive() | 线程是否存活(还没有运行完毕) | |
interrupt() | 打断线程 | 如果被打断线程正在sleep,wait,join会导致被打断的线程抛出InterruptedException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记;park的线程被打断,也会设置打断标记 |
interrrupted() | 判断当前线程是否被打断 | 会清除打断标记 |
currentThread() | 获取当前正在执行的线程 | |
sleep(long n) | 让当前执行的线程休眠n毫秒,休眠期间让出CPU的时间片给其他线程 | |
yield() | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@SneakyThrows
@Override
public void run() {
log.info("t1 start:"+Thread.currentThread().getName());
testForRun();
}
};
t1.run();
log.info("do continue......");
}
private static void testForRun(){
long start = System.currentTimeMillis();
long a = 1,b = 2;
for (long i = 0; i < 900000000; i++) {
a *=b;
}
long end = System.currentTimeMillis();
log.info("now.... ms:"+(end-start));
}
////////////////////////////////////////////////////
[main] INFO StartMethod - t1 start:main
[main] INFO StartMethod - now.... ms:695
[main] INFO StartMethod - do continue......可以看到run方法其实还是在main线程中运行,调用还是同步的
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@SneakyThrows
@Override
public void run() {
log.info("t1 start:"+Thread.currentThread().getName());
testForRun();
}
};
t1.start();
log.info("do continue......");
}
private static void testForRun(){
long start = System.currentTimeMillis();
long a = 1,b = 2;
for (long i = 0; i < 900000000; i++) {
a *=b;
}
long end = System.currentTimeMillis();
log.info("now.... ms:"+(end-start));
}
///////////////////////////////////////////////////
[main] INFO StartMethod - do continue......
[t1] INFO StartMethod - t1 start:t1
[t1] INFO StartMethod - now.... ms:689如果想要异步处理,还是直接使用start启动一个线程就好
run和start的区别:
调用sleep方法,会让当前线程从Runnable进入 Timed Waiting状态
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
t1.start();
log.info("t1 state:{}", t1.getState());
// [main] INFO SleepRunning - t1 state:RUNNABLE
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("t1 state:{}", t1.getState());
// [main] INFO SleepRunning - t1 state:TERMINATED
}其他线程可以使用 interrupt 方法打断正在睡眠的线程,这时sleep方法会抛出 InterruptedException
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
public void run() {
log.info("t1 start.. now sleeping....");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
log.info("t1 wake up");
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(500);
log.info("t1 end.. interrupt");
t1.interrupt();// 打断睡眠
}
///////////////////////////////////////////////////
[t1] INFO SR2 - t1 start.. now sleeping....
[main] INFO SR2 - t1 end.. interrupt
[t1] INFO SR2 - t1 wake up
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.renex.c2.SR2$1.run(SR2.java:15)睡眠结束后的线程未必会立刻得到执行
当前线程并未被分配时间片,时间片还在其他线程手中执行,需要等待其他线程的任务完成后才会被分配时间片
建议使用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

Runnable r1 = ()->{
int count = 0;
for (;;){
System.out.println("----> 1:\t"+count++);
}
};
Runnable r2 = ()->{
int count = 0;
for (;;){
// 使用yield调度,让当前运行线程进入Runnable状态,将优先权让给其他线程
Thread.yield();
System.out.println(" ----> 2:\t"+count++);
}
};
Thread t1 = new Thread(r1,"t1");
Thread t2 = new Thread(r2,"t2");
t1.start();
t2.start();截取一段输出
---> 1: 260323
----> 1: 260324
----> 1: 260325
----> 1: 260326
----> 1: 260327
----> 1: 260328
----> 2: 190640
----> 2: 190641
----> 2: 190642
----> 2: 190643
----> 1: 260329
----> 1: 260330
----> 2: 190644
----> 2: 190645
----> 2: 190646
----> 2: 190647
----> 2: 190648
----> 1: 260331
----> 1: 260332
----> 1: 260333
----> 2: 190649
----> 2: 190650
----> 2: 190651
----> 2: 190652
----> 2: 190653
----> 2: 190654可以看到线程1 比 线程2 执行的次数多得多
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度可以忽略它
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;Thread类中,默认最少线程是1,最多是10,默认5
如果CPU比较忙,那么优先级高的线程会获得更多的时间片,但CPU闲时,优先级几乎没作用
Runnable r1 = ()->{
int count = 0;
for (;;){
System.out.println("----> 1:\t"+count++);
}
};
Runnable r2 = ()->{
int count = 0;
for (;;){
// 使用yield调度,让当前运行线程进入Runnable状态,将优先权让给其他线程
// Thread.yield();
System.out.println(" ----> 2:\t"+count++);
}
};
Thread t1 = new Thread(r1,"t1");
Thread t2 = new Thread(r2,"t2");
// 设置不同线程的优先级
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();截取一段输出来看看
----> 1: 92363
----> 1: 92364
----> 1: 92365
----> 1: 92366
----> 2: 88323
----> 2: 88324
----> 1: 92367
----> 1: 92368
----> 1: 92369
----> 2: 88325
----> 2: 88326
----> 2: 88327
----> 2: 88328
----> 1: 92370
----> 1: 92371
----> 2: 88329
----> 2: 88330
----> 1: 92372
----> 1: 92373
----> 1: 92374
----> 1: 92375
----> 1: 92376
----> 1: 92377
----> 1: 92378
----> 1: 92379
----> 2: 88331
----> 2: 88332
----> 2: 88333
----> 2: 88334
----> 1: 92380
----> 1: 92381
----> 1: 92382
----> 1: 92383我们发现,线程间上下文切换好像并没有什么效果?因为 优先级 切换在CPU闲的时候几乎没什么用。只有在高负载情况下,这种效果才会慢慢体现出来。
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或sleep来让出cpu的使用权给其他程序
new Thread(()->{
while (true){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();以下是在linux(1CPU,2核)环境下运行的结果
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1334 root 20 0 2118192 23512 11460 S 99.7 4.9 0:08.48 javaPID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1380 root 20 0 2118192 26008 11344 S 1.7 5.4 0:00.47 java在认识join前,先研究以下代码,看看x的值是什么。
package com.renex.c2;
import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;
/**
* join方法
*/
@Slf4j(topic = "T3P")
public class T3P {
public static void main(String[] args) {
test();
}
static int x = 0;
private static void test(){
log.info("开始");
Thread t1 = new Thread(() -> {
log.info("内部线程开始");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("内部线程结束");
x=10;
});
t1.start();
log.info("x 的结果为:{}",x);
log.info("结束");
}
}
//////////////////////////////////////////////////
[main] INFO T3P - 开始
[Thread-0] INFO T3P - 内部线程开始
[main] INFO T3P - x 的结果为:0
[main] INFO T3P - 结束
[Thread-0] INFO T3P - 内部线程结束如果认为是0就调入陷阱了
分析:
log.info("内部线程开始");时,主线程已经执行到log.info("x 的结果为:{}",x);行了。那么这个时候主线程所得到的x值自然是0
解决方法:
用sleep行吗?可以,但是需要把握好线程1结束的时间,让其在主线程执行获得x值的前面就让x值=10
最好的办法是使用join方法,加在t1.start()后面
t1.start();
// 当主线程执行到这里,需要等待t1线程执行完毕,才可以继续执行
t1.join();
/////////////////////////////////////////
[main] INFO T3P - 开始
[t1] INFO T3P - 内部线程开始
[t1] INFO T3P - 内部线程结束
[main] INFO T3P - x 的结果为:10
[main] INFO T3P - 结束首先认知一个东西:

package com.renex.c2;
import lombok.extern.slf4j.Slf4j;
/**
* join方法
*/
@Slf4j(topic = "T4P")
public class T4P {
static int x = 0;
static int x2 = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
x=10;
}, "t1");
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
x2=20;
}, "t2");
t1.start();
t2.start();
long start = System.currentTimeMillis();
log.info("开始");
t1.join();
log.info("t1 join结束");
t2.join();
log.info("t2 join结束");
long end = System.currentTimeMillis();
log.info("r1:{},r2:{},耗时:{}",x,x2,end-start);
}
}
///////////////////////////////////////////////////////////////////
[main] INFO T4P - 开始
[main] INFO T4P - t1 join结束
[main] INFO T4P - t2 join结束
[main] INFO T4P - r1:10,r2:20,耗时:2这里不论t1还是t2线程休眠2秒,运行结果的耗时都是2秒
sleep(2;)),就可以往下运行了。

在此过程中,t2.join()仅需要等待1s

在此过程中,t1.join()并不需要等待
4.6.3 有时效的 join 其实就是给一个最大的等待时间,当等待达到设定的时间还没有运行结束,那么就不再等待了
Thread t1 = new Thread(() -> {
log.info("sleep.........");
try {
Thread.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1");
t1.start();
Thread.sleep(1);
t1.interrupt();
log.info("打断状态:{}",t1.isInterrupted());
//////////////////////////////////////////////
[t1] INFO T1I - sleep.........
Exception in thread "t1" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
at com.renex.c2.T1I.lambda$main$0(T1I.java:16)
at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.renex.c2.T1I.lambda$main$0(T1I.java:14)
... 1 more
[main] INFO T1I - 打断状态:false这里的打断,是打断sleep状态下的线程,在sleep状态下,返回的打断状态都是为false,但其实它是有打断的。
Thread t1 = new Thread(() -> {
while (true) {
Thread currented = Thread.currentThread();
boolean interrupted = currented.isInterrupted();
if (interrupted){
log.info("t1 线程被打断了!");
break;
}
}
}, "t1");
t1.start();
sleep(1);
t1.interrupt();
//////////////////////////////////////////////////////////////
[t1] INFO T1I - t1 线程被打断了!
Process finished with exit code 0**注意:**当线程被打断后,并不会结束线程的运行!
大多数情况,会出现的一种场景,线程1去终止线程2。
这个时候可以使用一种设计模式:两阶段终止模式(TwoPhaseTermination)
错误思路:
package com.renex.c2;
import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;
/**
* 打断线程
*/
@Slf4j(topic = "T2I")
public class T2I {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination t = new TwoPhaseTermination();
t.start();
sleep(5000);
t.stop();
}
}
@Slf4j(topic = "TwoPhaseTermination")
class TwoPhaseTermination{
private Thread monitor;
// 启动监控
public void start(){
monitor = new Thread(()->{
while (true){
Thread tList = Thread.currentThread();
if (tList.isInterrupted()){
log.error("准备结束代码");
break;
}
try {
sleep(1000);
log.info("执行监控记录");
} catch (InterruptedException e) {
/**
* 当出现在sleep阶段被打断,InterruptedException 会将打断标记设置为false
* 这就导致下一次循环监控时还是false
* 而这里执行了InterruptedException后肯定是已经被打断的,所以必须是true
* 所以这里还需要重新设置一次打断标记,这样再下一次循环中就可以检测出来
*/
// 重新设置打断标记
tList.interrupt();
e.printStackTrace();
}
}
},"monitor");
// 启动
monitor.start();
}
public void stop(){
monitor.interrupt();
}
}
/////////////////////////////////////////////////////
[monitor] INFO TwoPhaseTermination - 执行监控记录
[monitor] INFO TwoPhaseTermination - 执行监控记录
[monitor] INFO TwoPhaseTermination - 执行监控记录
[monitor] INFO TwoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.renex.c2.TwoPhaseTermination.lambda$start$0(T2I.java:36)
at java.lang.Thread.run(Thread.java:750)
[monitor] ERROR TwoPhaseTermination - 准备结束代码
Process finished with exit code 0LockSupport.park() 是Java中用于线程休眠的一种方法,它与 Thread.sleep() 和 Object.wait() 不同,因为它不会直接使线程进入休眠状态,而是首先检查是否有可用的许可证。如果有许可证,方法会立即返回,否则线程将进入休眠状态,直到发生以下三种情况之一:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.info("park....");
LockSupport.park();
log.info("unpark...");
log.info("打断状态:{}", Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
Thread.sleep(1);
t1.interrupt();
}
/////////////////////////////////////
[t1] INFO T3I - park....
[t1] INFO T3I - unpark...
[t1] INFO T3I - 打断状态:true还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
方法名 | static | 功能说明 |
|---|---|---|
stop() | 停止线程运行 | |
supend() | 挂起(暂停)线程运行 | |
resume() | 恢复线程运行 |
共同点:破坏同步代码块,容易造成对象的锁得不到释放,导致造成死锁问题
默认情况下,Java进程需要等待所有线程都运行结束,才会结束。
而有一种特殊的线程叫做守护线程:只要其他非守护线程运行结束了,即时守护线程的代码没有执行完,也会强制结束
Thread t1 = new Thread(() -> {
while (true){
if (Thread.currentThread().isInterrupted()){
break;
}
}
log.error("t1 结束");
}, "t1");
t1.start();
Thread.sleep(1000);
log.error("结束");
t1.interrupt();
///////////////////////////////////////////////////
[main] ERROR T3I - 结束
[t1] ERROR T3I - t1 结束Thread t1 = new Thread(() -> {
while (true){
if (Thread.currentThread().isInterrupted()){
break;
}
}
log.error("t1 结束");
}, "t1");
// 设置为守护线程
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.error("结束");
// 不用打断,理应是继续重复的运行
// t1.interrupt();
//////////////////////////////////////
[main] ERROR T3I - 结束可以看到,当主线程结束,理应继续运行的t1线程,也跟着结束了运行
注意:
从操作系统层面来说,分为了以下五种状态

初始状态
可运行状态(就绪状态)
运行状态
当CPU时间片用完,会从**
运行状态** 转换至**可运行状态**,会导致线程的上下文切换
阻塞状态
阻塞状态**可运行状态**运行状态的区别是,对阻塞状态**的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们终止状态
根据Thread.State枚举,分为了六种状态
public enum State{
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED
}
NEW
创建出来了一个线程,但是没有启动它
RUNNABLE
JavaAPI层面的RUNNABLE状态涵盖了 【操作系统】层面的**
可运行状态、运行状态、阻塞状态** 由于BIO导致的线程阻塞,在Java里无法区分,仍然认为是可运行的启动某个线程,那么该线程状态就被更改为了:RUNNABLE
BLOCKED、WAITING、TIMED_WAITING
TERMINATED

这里就是创建出两个线程,并行处理各自的事情,最后泡上一杯好茶
package com.renex.c2;
import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;
/**
* 泡茶
*/
@Slf4j(topic = "DemoTea")
public class DemoTea {
static Thread t1;
static Thread t2;
public static void main(String[] args) {
t1 = new Thread(() -> {
try {
log.info("洗水壶...");
sleep(1);
log.info("烧开水...");
sleep(15);
log.info("张三的活干完啦!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "张三");
t2 = new Thread(() -> {
try {
log.info("洗茶壶...");
sleep(1);
log.info("洗茶杯...");
sleep(2);
log.info("拿茶叶...");
sleep(1);
log.info("李四的活干完啦!!");
t1.join(); // 让t1等待一会
log.info("泡茶啦!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "李四");
t1.start();
t2.start();
}
}
//////////////////////////////////////////////
[李四] INFO DemoTea - 洗茶壶...
[张三] INFO DemoTea - 洗水壶...
[李四] INFO DemoTea - 洗茶杯...
[张三] INFO DemoTea - 烧开水...
[李四] INFO DemoTea - 拿茶叶...
[李四] INFO DemoTea - 李四的活干完啦!!
[张三] INFO DemoTea - 张三的活干完啦!!
[李四] INFO DemoTea - 泡茶啦!!更改需求:
情况一:
package com.renex.c2;
import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;
/**
* 泡茶
*/
@Slf4j(topic = "DemoTea")
public class DemoTea {
static Thread t1;
static Thread t2;
public static void main(String[] args) {
t1 = new Thread(() -> {
try {
log.info("洗水壶...");
sleep(1);
log.info("水壶洗好了...");
t2.join(); // 等待t2线程将茶叶拿过来
log.info("接过李四的茶叶...");
log.info("烧开水...");
sleep(15);
log.info("水已经烧开了!");
log.info("张三泡茶啦!!");
log.info("=================张三的活干完啦!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "张三");
t2 = new Thread(() -> {
try {
log.info("洗茶壶...");
sleep(1);
log.info("洗茶杯...");
sleep(2);
log.info("拿茶叶送给张三...");
sleep(1);
log.info("===============李四的活干完啦!!");
} catch (InterruptedException e) {
t2.interrupt();
e.printStackTrace();
}
}, "李四");
t1.start();
t2.start();
}
}
////////////////////////////////////////////////////////////
[张三] INFO DemoTea - 洗水壶...
[李四] INFO DemoTea - 洗茶壶...
[张三] INFO DemoTea - 水壶洗好了...
[李四] INFO DemoTea - 洗茶杯...
[李四] INFO DemoTea - 拿茶叶送给张三...
[李四] INFO DemoTea - ===============李四的活干完啦!!
[张三] INFO DemoTea - 接过李四的茶叶...
[张三] INFO DemoTea - 烧开水...
[张三] INFO DemoTea - 水已经烧开了!
[张三] INFO DemoTea - 张三泡茶啦!!
[张三] INFO DemoTea - =================张三的活干完啦!!情况二,就交给你们来做吧!