进程和线程
进程
- 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的
- 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
- 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)
线程
- 一个进程之内可以分为一到多个线程。
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
- Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。在 windows 中进程是不活动的,只是作为线程的容器
二者对比
- 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
- 进程拥有共享的资源,如内存空间等,供其内部的线程共享
- 进程间通信较为复杂
- 同一台计算机的进程通信称为 IPC(Inter-process communication)
- 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
- 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
并行和并发
并发
单核 cpu 下,线程实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给不同的线程使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是同时运行的。总结为一句话就是:微观串行
一般将这种轮流使用CPU的做法称作并发,concurrent.
并行
多个任务在同一时刻真正同时运行 ,必须依靠多核 CPU、多处理器硬件实现。 每个 CPU 核心独立执行一个线程,同一时间点有多条指令在不同物理核心上执行,是物理层面的同时执行。
异步和同步
同步调用是调用函数 / 接口后,调用线程卡住,原地阻塞等待,必须等被调用逻辑全部执行完毕、返回结果,当前线程才能继续往下执行。
异步调用指的是发起调用后,调用线程不会阻塞,立刻继续执行后续代码;被调用任务在后台独立运行。 等后台任务完成,通过回调函数、消息、通知等方式把结果返还给调用方。
创建和运行线程
1.使用Thread类创建线程
java
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "test1")
public class Test {
public static void main(String[] args) {
Thread thread1 = new Thread() {
public void run() {
log.info("running...");
}
};
thread1.start();
log.info("running...");
}
}

2.Runnable配合Thread创建线程
java
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "test1")
public class Test {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
log.info("running...");
}
};
Thread thread = new Thread(runnable, "t1");
thread.start();
log.info("running");
}
}
//也可以使用lambda简化
@Slf4j(topic = "test1")
public class Test {
public static void main(String[] args) {
Runnable runnable = () -> {log.info("running...");}
};
Thread thread = new Thread(runnable, "t1");
thread.start();
log.info("running");
}
}

3.使用Callable创建线程
java
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
@Slf4j(topic = "test1")
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> integerFutureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.info("running...");
Thread.sleep(1000);
return 100;
}
});
Thread thread = new Thread(integerFutureTask);
thread.start();
Integer i = integerFutureTask.get();
System.out.println("返回的结果为"+i);
}
}

常见的方法
1.start和run
start() 真正启动线程
run() 普通成员方法,不会开新线程
- 重写 run () 是写业务,start () 才是启动入口 我们重写 run () 写任务逻辑,但绝对不能手动调用 run;必须用 start 让操作系统调度执行。
- 为什么不能多次 start? 线程生命周期:新建→就绪→运行→终止。线程终止后生命周期结束,无法二次唤醒。
- 实现 Runnable 接口的场景 无论继承 Thread 还是实现 Runnable,规则完全一致:
start()开线程,run()只是普通方法。
java
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("执行线程:" + Thread.currentThread().getName());
});
System.out.println("主线程名:" + Thread.currentThread().getName());
// 方式1 start:新开线程
thread.start();
// 方式2 run:主线程执行
// thread.run();
}
}

2.sleep与yield
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 状态,然后调度执行其它同优先级的线程。如果这时没有同优先级的线程,那么不能保证让当前线程暂停的效果
- 具体的实现依赖于操作系统的任务调度器
3.join
join() 属于线程实例方法,A 线程调用 B.join () :A 线程阻塞等待,直到 B 线程执行完毕,A 才继续向下运行。 本质:利用wait实现等待,底层依赖等待通知机制。
-
阻塞对象 主线程 / 调用线程阻塞,被 join 的子线程正常跑。 例:main 调用
t.join()→ main 卡住,t 正常执行;t 跑完,main 放行。 -
可被 interrupt 中断 等待期间如果调用线程被
interrupt()打断,join 抛出InterruptedException。 -
限时版本
join(2000):最多等 2 秒。2 秒内子线程结束则提前放行;2 秒不管子线程跑没跑完,调用线程恢复执行。 -
底层原理 join 带
synchronized锁,锁是子线程对象; 内部循环判断线程是否存活,存活就wait(超时时间);子线程执行完退出时 JVM 会自动执行notifyAll唤醒等待线程。 -
会释放锁吗? join 内部调用
wait(),等待阶段会释放锁;sleep/yield 不会释放锁。
java
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("子线程执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
System.out.println("主线程开始等待子线程");
t.join(); // main阻塞,等t执行完
System.out.println("子线程结束,主线程继续运行");
}
}
4.interrupt
Java不会强制终止线程 ,interrupt() 只是给线程打一个中断标记位 (设为 true),相当于发通知:"有人希望你停止运行"。 最终要不要退出、怎么退出,由线程自身代码主动判断中断标识自行决定,属于协作式中断 。 旧方法stop()强制杀死线程,会导致锁、数据错乱,已被废弃。
- 线程正在阻塞(sleep /wait/join) 线程卡在
sleep()、Object.wait()、join()阻塞等待时,调用interrupt():
- 立刻唤醒线程
- 抛出
InterruptedException - 自动把中断标志重置为 false(清空标记)
2.线程正常运行(RUNNABLE) 仅把中断标记改为 true,不会抛异常、不会停线程,代码正常往下跑,需要线程手动判断标记自行结束。
java
Thread t = new Thread(()->{
while(true){
// 运行中,自己判断中断标记
if(Thread.currentThread().isInterrupted()){
System.out.println("收到中断,退出");
break;
}
}
});
t.start();
t.interrupt(); // 打标记
普通线程和守护线程
- 普通线程(用户线程 / User Thread,默认所有线程都是这类)
JVM 退出规则:只要任意一条普通用户线程还在运行,JVM 就不会关闭。 主线程、手动 new Thread 创建的线程默认都是用户线程。 主线程结束后,如果还有用户子线程在跑,程序不会终止。
- 守护线程(Daemon Thread,后台线程)
作用:为普通用户线程提供后台服务,比如垃圾回收 GC、定时任务、心跳检测。 JVM 退出规则:所有普通用户线程全部执行完毕,无论守护线程是否还在运行,JVM 直接强制退出。
java
Thread t = new Thread(...);
t.setDaemon(true); // 设为守护线程
t.start();
线程的生命周期
Java 线程一共 6 种固定状态,任何时刻线程只处于其中一种状态,状态之间只能按规则流转。
java
// JDK源码枚举全部6种状态
public enum State {
NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
}

- NEW 新建状态
- 执行
new Thread()创建线程对象,还没有调用 start ()。 - 仅创建 Java 对象,操作系统内核线程未创建,没有分配栈。
- 只能调用
start()进入下一阶段;调用多次 start 直接报错。
-
RUNNABLE 可运行
-
就绪 Ready:线程准备好了,等待 CPU 调度分配时间片;
-
运行 Running:抢到 CPU 时间片,正在执行 run () 代码。
触发进入 RUNNABLE:
- 调用
start(); - sleep 时间结束、wait 被唤醒、锁竞争成功;
- yield 让出 CPU 后回到 RUNNABLE。
- BLOCKED 阻塞(synchronized 锁阻塞)
只有一种场景进入:线程去抢 synchronized 同步锁失败,锁被别的线程占用,线程卡在锁池等待。
- 只和
synchronized有关;Lock 锁不会进入 BLOCKED(Lock 用 AQS,属于 WAITING)。 - 持有锁的线程释放锁后,该线程重新回到 RUNNABLE 抢锁。
- WAITING 无限等待
线程无限期等待,必须被手动唤醒,不会自己恢复。 进入方式(3 种):
Object.wait()无参;Thread.join()无参;- Lock 的
lockSupport.park()。
唤醒条件:
- wait:其他线程执行
notify()/notifyAll(); - join:被等待的线程执行完毕;
- park:unpark 唤醒。 唤醒后回到 RUNNABLE。
- TIMED_WAITING 限时等待
有固定等待时长,时间到自动唤醒;也可提前被唤醒。 常用方法:
Thread.sleep(毫秒)Object.wait(long)带超时Thread.join(long)带超时- LockSupport.parkNanos / parkUntil
sleep、join 限时版本都属于这个状态。
- TERMINATED 终止 / 死亡
线程 run 方法正常执行完毕,或者异常跑完,生命周期彻底结束。
- 终止后的线程不能再次 start (),报非法状态异常;
- 无法复活。
Synchronized
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
1.阻塞式的解决方案:synchronized,Lock
2.非阻塞式的解决方案:原子变量
核心语法:
java
synchronized(object)
{
临界区
}
原理:
synchronized实际是使用了对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
方法上的synchronized:
成员方法:
java
class Test{
public synchronized void test() {
}
}
//等价于
class Test{
public void test() {
synchronized(this) {
}
}
}
静态方法:
java
class Test{
public synchronized static void test() {
}
}
//等价于
class Test{
public static void test() {
synchronized(Test.class) {
}
}
}
变量的线程安全
局部变量线程安全分析
java
public static void test1(){
int i = 10;
i++;
}
每个线程调用test1()时创建局部变量,会在每个线程的栈帧内存中被创建多份,存入局部变量表中,因此不存在共享,也就是线程安全的

java
class ThreadSafe {
public void method1(int loopNumber) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {
method2(list);
method3(list);
}
}
public void method2(ArrayList<String> list) {
list.add("1");
}
public void method3(ArrayList<String> list) {
list.remove(0);
}
}
//线程不安全
class ThreadSafeSubClass extends ThreadSafe{
@Override
public void method3(ArrayList<String> list) {
new Thread(() -> {
list.remove(0);
}).start();
}
}
常见的线程安全类
线程安全类指的是多个线程调用他们同一个实例的某个方法时,是线程安全的
1.String
2.Integer
String,Integer等都是不可变类,所以是线程安全的
3.StringBuffer
4.Random
5.Vector
6.Hashtable
7.JUC包下的类
Synchronized底层
Java对象头
以32位虚拟机为例:


Monitor(监视器|管程)
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针
Monitor结构如下:

part和unpart的使用
LockSupport.park():阻塞当前线程,休眠停下
LockSupport.unpark(thread):唤醒指定被阻塞的线程
java
import java.util.concurrent.locks.LockSupport;
public class ParkDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("线程开始运行,准备阻塞");
// 当前线程停下、阻塞
LockSupport.park();
System.out.println("线程被唤醒,继续执行");
});
t1.start();
// 主线程休眠2秒之后唤醒t1线程
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(t1);
}
}
与 Object 的 wait & notify 相比
- wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 unpark 不必
- park & unpark 是以线程为单位来阻塞和唤醒线程,而 notify 只能随机唤醒一个等待线程,notify
底层原理
每个线程都有自己的一个 Parker 对象,由三部分组成 _counter, _cond 和 _mutex 打个比喻
- 线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
- 调用 park 就是要看需不需要停下来歇息
- 如果备用干粮耗尽,那么钻进帐篷歇息
- 如果备用干粮充足,那么不需停留,继续前进
- 调用 unpark,就好比令干粮充足
- 如果这时线程还在帐篷,就唤醒让他继续前进
- 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
- 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮

重新理解线程状态转换

NEW(新建):线程对象创建,还没有调用start()
RUNNABLE(可运行):细分就绪 (等待 CPU 调度)、正在 CPU 运行
BLOCKED(阻塞):等待 synchronized 监视器锁
WAITING(无限等待):无时限休眠,需要手动唤醒
TIMED‑WAITING(限时等待):带超时时间的休眠
TERMINATED(终止):线程执行完毕
1、编号 1:NEW → RUNNABLE
调用 thread.start(),线程进入就绪 (RUNNABLE) 状态,等待操作系统分配 CPU 时间片。
2、编号 2:RUNNABLE → WAITING
触发方法:
obj.wait()(synchronized 锁内)thread.join()LockSupport.park()线程进入无限等待,需要别的线程唤醒。
3、编号 3:WAITING → RUNNABLE
唤醒条件:
notify()/notifyAll()唤醒 wait 线程- join 的目标线程执行完毕
LockSupport.unpark(线程)唤醒之后线程回到可运行状态
4、编号 4:WAITING ←→ TIMED‑WAITING
RUNNABLE → TIMED‑WAITING(编号 5)
限时休眠进入超时等待: sleep(long)、wait(time)、join(time)、LockSupport.parkNanos()
TIMED‑WAITING → RUNNABLE(编号 6)
- 等待时间超时自动苏醒
- 中途被其他线程提前唤醒
5、编号 7:RUNNABLE → TIMED‑WAITING
调用sleep(毫秒),线程进入限时等待
6、编号 8:TIMED‑WAITING → RUNNABLE
睡眠时间结束,线程恢复就绪状态
7、编号 9:RUNNABLE ↔ BLOCKED
- RUNNABLE → BLOCKED :线程去争抢
synchronized对象锁失败,进入锁阻塞队列 - BLOCKED → RUNNABLE:锁被释放,该线程抢到监视器锁,变为就绪状态
8、编号 10:RUNNABLE → TERMINATED
线程 run () 方法代码执行完毕、或者异常结束,线程进入终止状态,生命周期结束。
线程的活跃性
死锁
多个线程互相持有对方需要的锁,双方都不肯释放自己手上的锁,互相僵持、永久阻塞,这就是死锁。
线程 A:拿着锁 1,等待获取锁 2 线程 B:拿着锁 2,等待获取锁 1 两个线程卡住,谁都无法往下执行。
产生死锁的 4 个必要条件
- 互斥条件:同一时间,锁只允许一个线程占用(synchronized、ReentrantLock 都是排他锁)
- 请求与保持:线程已经持有一把锁,现在又去申请另一把新锁,旧锁不会释放
- 不可剥夺:别人手上的锁,不能被强制抢走,只能持有者主动释放
- 循环等待:线程之间形成环路等待:A 等 B、B 等 C、C 等 A
java
public class DeadLockDemo {
public static void main(String[] args) {
Object lockA = new Object();
Object lockB = new Object();
//线程1:先拿A,再请求B
new Thread(() -> {
synchronized (lockA){
System.out.println("线程1拿到锁A");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (lockB){
System.out.println("线程1拿到锁B");
}
}
}).start();
//线程2:先拿B,再请求A
new Thread(() -> {
synchronized (lockB){
System.out.println("线程2拿到锁B");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (lockA){
System.out.println("线程2拿到锁A");
}
}
}).start();
}
}
怎么定位死锁
1.根据jps查看java线程数据

2.在根据jstack查看线程内的具体情况

活锁
活锁是两个线程互相改变了对方的结束条件,导致谁也无法结束线程
java
public class TestLiveLock {
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 期望减到 0 退出循环
while (count > 0) {
sleep(0.2);
count--;
log.debug("count: {}", count);
}
}, "t1").start();
new Thread(() -> {
// 期望超过 20 退出循环
while (count < 20) {
sleep(0.2);
count++;
log.debug("count: {}", count);
}
}, "t2").start();
}
}
饥饿
多个线程争抢锁,部分线程长期拿不到锁、一直处于等待,永远得不到执行机会,线程被 "饿死"。
死锁:线程互相卡住,全都不动;
活锁:线程反复尝试、反复失败;
饥饿:有的线程一直抢不到资源
ReentrantLock
属于 java.util.concurrent.locks,可重入独占锁,JDK 层面实现;
对比 synchronized:synchronized 是虚拟机底层监视器锁。
ReentrantLock具备以下条件:
1.可中断
2.可以设置超时时间
3.可以设置为公平锁
4.支持多个条件变量
与sychronized一样,都支持可重入
可充入指的是同一个线程如果首次获得了这把锁,那么因为他是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获取这把所的时候,自己也会被锁住
java
reenreantlock.lock();
try{
//临界锁
}finally{
//释放锁
reentrantLock.unlock();
}
锁超时
java
@Slf4j
public class Demo {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
new Thread(()->{
try{
if(!reentrantLock.tryLock(5, TimeUnit.SECONDS)){
log.info("t1获取不到锁");
return;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("t1获取到锁了");
}).start();
new Thread(()->{
try{
if(reentrantLock.tryLock(5, TimeUnit.SECONDS)){
log.info("t2获取到锁了");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
}
}
公平锁
线程按照请求锁的先后顺序排队获取锁,遵守 FIFO,禁止线程插队,不会出现线程饥饿。
synchronized 监视器锁永远是非公平锁,不支持排队策略,无法设置为公平。
ReentrantLock默认是不公平的。我们可以指定参数将其变成公平锁。
java
// true = 公平锁;不传参数/false = 非公平锁
ReentrantLock lock = new ReentrantLock(true);
非公平锁对比
非公平:新来线程可以直接尝试抢占锁,插队成功就跳过排队线程
优点:吞吐量高、速度快
缺点:部分老线程长期抢不到锁,发生线程饥饿
条件变量
Condition 就是锁配套的等待‑通知工具,基于 ReentrantLock 创建,用来代替 synchronized 的 wait()‑notify()。
await()执行后会释放锁,进入conditionObject等待,被唤醒时重新竞争lock锁
java
@Slf4j
public class Demo2 {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
new Thread(()->{
try{
if(!lock.tryLock(5, TimeUnit.SECONDS)){
condition1.await();
}
log.info("t1获取锁成功");
condition1.signalAll();
}catch(Exception e){
e.printStackTrace();
}
}).start();
new Thread(()->{
try{
if(!lock.tryLock(5, TimeUnit.SECONDS)){
condition1.await();
}
log.info("t2获取锁成功");
condition1.signalAll();
}catch(Exception e){
e.printStackTrace();
}
}).start();
}
}
固定线程运行顺序
先打印2,在打印1
1。notify&wait
java
@Slf4j
public class Demo3 {
//同步锁对象
static Object object = new Object();
public static void main(String[] args) {
AtomicBoolean flag = new AtomicBoolean(false);
new Thread(()->{
synchronized (object){
try{
while(!flag.get()){
log.info("t1获取锁失败");
object.wait();
}
log.info("t1获取锁成功");
object.notifyAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t1").start();
new Thread(()->{
synchronized (object){
log.info("t2获取锁成功");
flag.set(true);
object.notifyAll();
}
},"t2").start();
}
}
2.part&unpart
java
@Slf4j
public class Demo3 {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
LockSupport.park();
log.info("t1执行了");
},"t1");
Thread t2 = new Thread(()->{
log.info("t2执行了");
LockSupport.unpark(t1);
},"t2");
t1.start();
t2.start();
}
}
交替输出的三种实现
1.nodify&wait
java
@Slf4j
public class Demo4 {
static Object object = new Object();
static boolean t1 = true,t2 = false,t3 = false;
static int cnt = 15;
public static void main(String[] args) {
new Thread(()->{
for(int i=0;i<5;i++) {
synchronized (object) {
try {
while (!t1) {
object.wait();
}
log.info("a");
t1 = false;
t2 = true;
object.notifyAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
new Thread(()->{
for(int i=0;i<5;i++) {
synchronized (object) {
try {
while (!t2) {
object.wait();
}
log.info("b");
t2 = false;
t3 = true;
object.notifyAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
new Thread(()->{
for(int i=0;i<5;i++){
synchronized (object){
try {
while(!t3){
object.wait();
}
log.info("c");
t3 = false;
t1 = true;
object.notifyAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
}
}
2.await&signal
java
@Slf4j
public class Demo4 {
// 创建可重入锁
private static final ReentrantLock lock = new ReentrantLock();
// 三个独立的条件队列,分别管控 a、b、c线程
private static final Condition condA = lock.newCondition();
private static final Condition condB = lock.newCondition();
private static final Condition condC = lock.newCondition();
// 执行标记
private static int flag = 1; //1打印a,2打印b,3打印c
public static void main(String[] args) {
// 线程A 打印a
new Thread(() -> {
for (int i = 0; i < 5; i++) {
lock.lock();
try {
while (flag != 1) {
// 不该当前线程执行,进入A条件队列等待
condA.await();
}
log.info("a");
flag = 2;
// 唤醒b线程
condB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}, "线程A").start();
// 线程B 打印b
new Thread(() -> {
for (int i = 0; i < 5; i++) {
lock.lock();
try {
while (flag != 2) {
condB.await();
}
log.info("b");
flag = 3;
condC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}, "线程B").start();
// 线程C 打印c
new Thread(() -> {
for (int i = 0; i < 5; i++) {
lock.lock();
try {
while (flag != 3) {
condC.await();
}
log.info("c");
flag = 1;
condA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}, "线程C").start();
}
}
3.part&unpart
java
@Slf4j
public class Demo4 {
public static void main(String[] args) {
Thread tA = null, tB = null, tC = null;
tA = new Thread(() -> {
for (int i = 0; i < 5; i++) {
LockSupport.park();
log.info("a");
LockSupport.unpark(tB);
}
}, "A");
tB = new Thread(() -> {
for (int i = 0; i < 5; i++) {
LockSupport.park();
log.info("b");
LockSupport.unpark(tC);
}
}, "B");
tC = new Thread(() -> {
for (int i = 0; i < 5; i++) {
LockSupport.park();
log.info("c");
LockSupport.unpark(tA);
}
}, "C");
tA.start();
tB.start();
tC.start();
// 主线程最先唤醒A,开启循环
LockSupport.unpark(tA);
}
}