Wait和Notify用于等待。其原理为:
obj.wait()
让进入 object 监视器的线程到 waitSet 等待,注意必须是获得对象锁的像锁的线程才能调用。wait方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到 notify 为止obj.wait(long n)
有时限的等待, 到 n 毫秒后结束等待,或是被 notifyobj.notify()
在 object 上正在 waitSet 等待的线程中挑一个唤醒obj.notifyAll()
让 object 上正在 waitSet 等待的线程全部唤醒 final static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
}).start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
}).start();
sleep(2);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
obj.notify();
// obj.notifyAll();
}
}
notify 的结果:
20:00:53.096 [Thread-0] c.TestWaitNotify - 执行....
20:00:53.099 [Thread-1] c.TestWaitNotify - 执行....
20:00:55.096 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
20:00:55.096 [Thread-0] c.TestWaitNotify - 其它代码....
notifyAll 的结果:
19:58:15.457 [Thread-0] c.TestWaitNotify - 执行....
19:58:15.460 [Thread-1] c.TestWaitNotify - 执行....
19:58:17.456 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
19:58:17.456 [Thread-1] c.TestWaitNotify - 其它代码....
19:58:17.456 [Thread-0] c.TestWaitNotify - 其它代码....
synchronized(lock){
while (条件不成立) { //方法之虚假唤醒
lock.wait(); //进入等待状态
}
}
synchronized(lock){
lock.notifyAll(); //唤醒所有,再通过条件判断唤醒的是否是自己
}
保护性暂停即 Guarded Suspension,用在一个线程等待另一个线程的执行结果
该模式的实现主要依靠了wait和notifyAll方法,和上面的使用类似
/**
* 保护性暂停设计模式实现
*/
class GuardedObject {
private Object response;
private final Object lock = new Object();
public Object get(){
synchronized (lock){
while (response == null){
try {
lock.wait(); //等待唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return response;
}
public void put(Object obj){
synchronized (lock){
response = obj;
lock.notifyAll(); //唤醒
}
}
}
测试:
public static void main(String[] args) {
GuardedObject guarded = new GuardedObject();
//t1线程等待response结果
new Thread(()->{
System.out.println("t1等待response");
Object obj = guarded.get();
System.out.println("t1获得response");
System.out.println("response = " + (String)obj);
}, "t1").start();
//t2线程设置response结果
new Thread(()->{
try {
Thread.sleep(2000);
System.out.println("t2设置response");
guarded.put("123");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
}
结果:
t1等待response
t2设置response
t1获得response
response = 123
设置等待一段时间后仍没有收到response就自动唤醒
/**
* 保护性暂停设计模式实现
*/
class GuardedObject {
private Object response;
private final Object lock = new Object();
public Object get(long timeout){
synchronized (lock){
long begin = System.currentTimeMillis(); //开始时间
long timePassed = 0;
while (response == null){
long lastTime = timeout - timePassed; //剩余时间
if(lastTime <= 0)
break;
try {
lock.wait(lastTime); //等待唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
timePassed = System.currentTimeMillis() - begin; //已过去的时间
}
}
return response;
}
public void put(Object obj){
synchronized (lock){
response = obj;
lock.notifyAll(); //唤醒
}
}
}
Join的实现原理其就是我们上面的带有超时效果的保护性暂停,其中只有两点不同:
下面是join方法的源码
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) { //如果millis=0,表示不设置超时时间
while (isAlive()) { //判断线程是否存活
wait(0);
}
} else {
while (isAlive()) { //判断线程是否存活
long delay = millis - now; //剩余时间
if (delay <= 0) {
break;
}
wait(delay); //等待delay时间
now = System.currentTimeMillis() - base; //已经过的时间
}
}
}
引入:图中 Futures 就好比居民楼一层的信箱(每个信箱有房间编号),左侧的 t0,t2,t4 就好比等待邮件的居民,右 侧的 t1,t3,t5 就好比邮递员
分析:如果需要在多个类之间使用 GuardedObject 对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类, 这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理。
实现:
新增 id 用来标识 Guarded Object
class GuardedObject {
//多个GuardedObject时用于标识
private int id;
private Object response;
public GuardedObject(int id) {
this.id = id;
}
public Object get(long timeout){
synchronized (this){
long begin = System.currentTimeMillis(); //开始时间
long timePassed = 0;
while (response == null){
long lastTime = timeout - timePassed; //剩余时间
if(lastTime <= 0)
break;
try {
this.wait(lastTime); //等待唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
timePassed = System.currentTimeMillis() - begin; //已过去的时间
}
}
return response;
}
public void put(Object obj){
synchronized (this){
response = obj;
this.notifyAll(); //唤醒
}
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
中间解耦类:使用线程安全的Map来存储GuardedObject,用于解耦
class MailBoxs{
private static Map<Integer, GuardedObject> map = new Hashtable();
private static int i = 1;
//生成唯一id
private static synchronized int generateId(){
return i++;
}
//生成GuardedObject
public static GuardedObject createGuardedObject(){
GuardedObject go = new GuardedObject(generateId());
map.put(go.getId(), go);
return go;
}
public static GuardedObject getGuardedObject(int i){
return map.remove(i);
}
public static Set<Integer> getIds(){
return map.keySet();
}
}
业务相关类
//收信人
class People extends Thread{
@Override
public void run() {
GuardedObject go = MailBoxs.createGuardedObject();
System.out.println("开始收信" + go.getId());
go.get(20000);
System.out.println("收到信" + go.getId());
}
}
//送信人
class Postman extends Thread{
private int id;
private String mail;
public Postman(int id, String mail) {
this.id = id;
this.mail = mail;
}
@Override
public void run() {
GuardedObject go = MailBoxs.getGuardedObject(id);
System.out.println("开始送信" + go.getId() + "内容为" + mail);
go.put(mail);
}
}
测试
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++){
new People().start();
}
Thread.sleep(3000);
for (int id : MailBoxs.getIds()){
new Postman(id, "内容"+id).start();
}
}
结果
开始收信1
开始收信2
开始收信3
开始送信2内容为内容2
开始送信3内容为内容3
收到信2
收到信3
开始送信1内容为内容1
收到信1
要点:用于线程间通信的一种异步模式
消息类,包含id和消息体
//线程安全的消息类
final class Massage{
private int id;
private Object value;
//没有set方法,只能创建时初始化
public Massage(int id, Object value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public Object getValue() {
return value;
}
@Override
public String toString() {
return "Massage{" +
"id=" + id +
", value=" + value +
'}';
}
}
异步消息队列实现
class MassageQueue{
//消息队列
private LinkedList<Massage> queue = new LinkedList<>();
//消息队列的容量
private int capcity;
public MassageQueue(int capcity) {
this.capcity = capcity;
}
//消费者消费消息
public Massage get(){
synchronized (queue){
//检查队列是否为空
while (queue.isEmpty()) {
try {
System.out.println("消费队列已空");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//从队列的头部获取元素返回
Massage massage = queue.removeFirst();
System.out.println("消费者消息"+massage.getId());
queue.notifyAll();
return massage;
}
}
//生产者者生产消息
public void put(Massage massage){
synchronized (queue) {
//检查队列是否已满
while (queue.size() >= capcity) {
try {
System.out.println("消息队列是否已满");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "生产消息"+massage.getId());
queue.addLast(massage);
queue.notifyAll();
}
}
}
测试:创建了三个生产者,一个消费者,消息队列的容量为2
public static void main(String[] args) {
MassageQueue queue = new MassageQueue(2);
for (int i = 1; i <= 3; i++) {
int finalI = i;
new Thread(()->{
queue.put(new Massage(finalI, "消息"+finalI));
}, "生产者"+i).start();
}
new Thread(()->{
while (true){
try {
Thread.sleep(1000);
Massage massage = queue.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者").start();
}
结果:
生产者3生产消息3
生产者2生产消息2
消息队列是否已满
消费者消息3
生产者1生产消息1
消费者消息2
消费者消息1
消费队列已空
它们是 LockSupport 类中的方法,用于暂停和唤醒线程
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象);
先pack再unpack
Thread t1 = new Thread(() -> {
log.debug("start...");
sleep(1);
log.debug("park...");
LockSupport.park();
log.debug("resume...");
}, "t1");
t1.start();
sleep(2);
log.debug("unpark...");
LockSupport.unpark(t1);
结果:
18:42:52.585 c.TestParkUnpark [t1] - start...
18:42:53.589 c.TestParkUnpark [t1] - park...
18:42:54.583 c.TestParkUnpark [main] - unpark...
18:42:54.583 c.TestParkUnpark [t1] - resume...
先unpack再pack
Thread t1 = new Thread(() -> {
log.debug("start...");
sleep(2);
log.debug("park...");
LockSupport.park();
log.debug("resume...");
}, "t1");
t1.start();
sleep(1);
log.debug("unpark...");
LockSupport.unpark(t1);
结果:同样可以解锁,没有顺序要求
18:43:50.765 c.TestParkUnpark [t1] - start...
18:43:51.764 c.TestParkUnpark [main] - unpark...
18:43:52.769 c.TestParkUnpark [t1] - park...
18:43:52.769 c.TestParkUnpark [t1] - resume...
特点
与 Object 的 wait & notify 相比
park unpark 原理
每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _cond 和 _mutex
1、当前线程调用 Unsafe.park() 方法时(先于unpark)
(2)检查 _counter ,如果为 0,获得 _mutex 互斥锁
(3)线程进入 _cond 条件变量阻塞
(1)调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
(2)唤醒 _cond 条件变量中的 Thread_0
(3) Thread_0 恢复运行
(4) 设置 _counter 为 0
3、先调用 调用 Unsafe.unpark(Thread_0) 方法,再调用park方法
t.start()
方法时,由 NEW --> RUNNABLE
2、RUNABL<–>WATING
t 线程用 synchronized(obj)
获取了对象锁后
obj.wait()
方法时,t 线程从RUNNABLE --> WAITING
obj.notify()
,obj.notifyAll()
,t.interrupt()
时
WAITING --> RUNNABLE
,线程进入Monitor中EntryListWAITING --> BLOCKED
,Monitor中Owner指向该线程3、RUNNABLE <–> WAITING
t.join()
方法时,当前线程从 RUNNABLE --> WAITING
interrupt()
时,当前线程从 WAITING --> RUNNABLE
4、RUNNABLE <–> WAITING
LockSupport.park()
方法会让当前线程从 RUNNABLE --> WAITING
LockSupport.unpark(目标线程)
或调用了线程 的interrupt()
,会让目标线程从 WAITING --> RUNNABLE
5、RUNNABLE <–> TIMED_WAITING
t 线程用 synchronized(obj)
获取了对象锁后
obj.wait(long n)
方法时,t 线程从 RUNNABLE --> TIMED_WAITING
obj.notify()
, obj.notifyAll()
, t.interrupt()
时
TIMED_WAITING --> RUNNABLE
TIMED_WAITING --> BLOCKED
6、 RUNNABLE <–> TIMED_WAITING
t.join(long n)
方法时,当前线程从 RUNNABLE --> TIMED_WAITING
interrupt()
时,当前线程从 TIMED_WAITING --> RUNNABLE
7、RUNNABLE <–> TIMED_WAITING
当前线程调用 Thread.sleep(long n)
,当前线程从 RUNNABLE --> TIMED_WAITING
当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING --> RUNNABLE
8、RUNNABLE <–> TIMED_WAITING
LockSupport.parkNanos(long nanos)
或 LockSupport.parkUntil(long millis)
时,当前线程从 RUNNABLE --> TIMED_WAITING
LockSupport.unpark(目标线程)
或调用了线程 的 interrupt()
,或是等待超时,会让目标线程从 TIMED_WAITING--> RUNNABLE
9、 RUNNABLE <–> BLOCKED
synchronized(obj)
获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED
BLOCKED --> RUNNABLE
,其它失败的线程仍然 BLOCKED10、RUNNABLE <–> TERMINATED
将锁的粒度细分
活跃性关注的是“某件正确的事情最终会发生”。
例如,如果A线程等待B线程释放其持有的资源,而B线程永远都不释放该资源,那么线程A就会永远的等待下去。这样就不具备活跃性。
一个线程需要同时获取多把锁,这时就容易发生死锁,例如:t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁 。t2 线程 获得 B对象 锁,接下来想获取 A对象 的锁 如:
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
log.debug("lock A");
sleep(1);
synchronized (B) {
log.debug("lock B");
log.debug("操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
log.debug("lock B");
sleep(0.5);
synchronized (A) {
log.debug("lock A");
log.debug("操作...");
}
}
}, "t2");
t1.start();
t2.start();
上面的代码永远都不会执行完毕,因为发生了死锁。
检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁
首先使用jps查看进程ID
再使用jstack <进程ID>
,查看当前的状态:
同样使用jconsole可以查看当前是否存在死锁:连接当前进程,点击线程---->检测死锁。
活锁出现在两个线程互相改变对方的结束条件,后谁也无法结束,例如
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
while (count > 0) {
try {
Thread.sleep(200);
count--;
System.out.println("t1 count: " + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
new Thread(() -> {
while (count < 20) {
try {
Thread.sleep(200);
count++;
System.out.println("t2 count: " + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2").start();
}
上面程序虽然没有互相持有资源,但是仍然会一直运行下去,这是因为产生了活锁。
在并发应用程序中,通过等待随机长度的时间或者回退可以有效避免活锁的发生。
很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不 易演示,讲读写锁时会涉及饥饿问题
先来看看使用顺序加锁的方式解决之前的死锁问题
顺序加锁的解决方案
但是顺序加锁很有可能产生饥饿。
有五位哲学家,围坐在圆桌旁。
哲学家进餐进餐问题如果不加于干预就很容易产生死锁问题,如果使用顺序加锁的方法解决死锁又很容易产生饥饿的现象。这时候就需要使用ReentrantLock来解决
相对于 synchronized 它具备如下特点
与 synchronized 一样,都支持可重入
基本语法:
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
示例:
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
m1();
}
public static void m1(){
lock.lock();
try {
System.out.println("m1 in");
m2();
} finally {
lock.unlock();
}
}
public static void m2() {
lock.lock();
try {
System.out.println("m2 in");
} finally {
lock.unlock();
}
}
结果如下图所示,m1和m2中都对同一个ReentrantLock对象加了锁,说明可能重入没有问题。
可打断表示加锁时如果失败进入阻塞队列,则可以进行打断,可以使用ReentrantLock::lockInterruptibly()
方法设置,示例:
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
System.out.println("尝试加锁");
lock.lockInterruptibly();//设置可打断锁
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("加锁失败,被打断");
return;
}
try {
System.out.println("获得锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
t1.start();
Thread.sleep(1000);
System.out.println("打断加锁");
t1.interrupt();
}
锁超时是指尝试加锁时,如果超过一段时间仍然获得不到锁就会自动放弃加锁,可以使用使用ReentrantLock::trylock()
方法设置,示例:
1)立即失败
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
System.out.println("t1尝试获得锁");
if (!lock.tryLock()) {
System.out.println("获得锁失败, 返回");
return;
}
try {
System.out.println("t1获得锁成功");
} finally {
lock.unlock();
}
}, "t1");
System.out.println("main获得锁");
lock.lock();
t1.start();
}
结果:
2)超时失败
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
System.out.println("t1尝试获得锁");
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("获得锁失败, 返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("被打断,返回");
return;
}
try {
System.out.println("t1获得锁成功");
} finally {
lock.unlock();
}
}, "t1");
System.out.println("main获得锁");
lock.lock();
t1.start();
Thread.sleep(2000);
lock.unlock();
}
使用synchronized时会出现死锁
可以更改为使用ReentrantLock::trylock()
方法,这样就不会出现场死锁
这里的公平是指阻塞队列(EntryList)中的线程按照先进先出的顺序获得锁,ReentrantLock和Synchronized 默认是不公平的,也就是阻塞队列中的线程通过争抢的方式获得锁。
ReentrantLock可以使用通过构造函数ReentrantLock lock = new ReentrantLock(false);
设置为公平锁,公平锁一般没有必要,会降低并发度,后面分析原理时会讲解
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待 ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
使用要点:
语法:
ReentrantLock lock = new ReentrantLock(); //创建ReentrantLock对象
Condition condition = lock.newCondition();//创建条件变量对象
new Thread(()->{
lock.lock(); //加锁
try {
condition.await(); //condition条件不满足,进入等待队列。与wait对应
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
Thread.sleep(1000);
condition.signal(); //唤醒等待condition条件变量的线程,与Notify对应
比如,必须先 2 后 1 打印
1)wait notify 版
//用来同步的对象
static Object obj = new Object();
// t2 运行标记, 代表 t2 是否执行过
static boolean t2runed = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (obj) {
// 如果 t2 没有执行过
while (!t2runed) {
try {
// t1 先等一会
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(1);
});
Thread t2 = new Thread(() -> {
System.out.println(2);
synchronized (obj) {
//修改运行标记
t2runed = true;
// 通知 obj 上等待的线程(可能有多个,因此需要用 notifyAll)
obj.notifyAll();
}
});
t1.start();
t2.start();
}
2)park unpark版
可以看到,上面的实现上很麻烦:
park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』, 不需要『同步对象』和『运行标记』
可以使用 LockSupport 类的 park 和 unpark 来简化上面的题目:
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("1");
LockSupport.park(); //暂停等待
}, "t1");
t1.start();
new Thread(()->{
System.out.println("2");
LockSupport.unpark(t1); //唤醒t1
}, "t2").start();
}
线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现
1) wait notify 版
打印控制类,用flag变量控制本次应该打印的a,loopNums控制打印次数
class SyncWaitNotify{
//用于标记本次需要唤醒线程
private int flag;
//打印次数
private int loopNums;
public SyncWaitNotify(int flag, int loopNums) {
this.flag = flag;
this.loopNums = loopNums;
}
public void print(String msg, int flag){
for (int i = 0; i < loopNums; i++) {
synchronized (this) {
while (this.flag != flag){ //如果自己不满足条件,就一直等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(msg);
this.flag = (flag + 1) % 3; //设置下一次唤醒的flag
this.notifyAll(); //唤醒所有
}
}
}
}
测试:
public static void main(String[] args) {
SyncWaitNotify waitNotify = new SyncWaitNotify(0, 5);
new Thread(()->{
waitNotify.print("a", 0);
}).start();
new Thread(()->{
waitNotify.print("b", 1);
}).start();
new Thread(()->{
waitNotify.print("c", 2);
}).start();
}
结果:
2)、Await和Signal实现
使用Condition作为条件变量,使用await和Signal用于等待和唤醒
class SyncAwaitSignal extends ReentrantLock {
private int loopNums;
public SyncAwaitSignal(int loopNums) {
this.loopNums = loopNums;
}
public void print(String msg, Condition current, Condition next){
for (int i = 0; i < loopNums; i++) {
lock();
try {
//等待信号
current.await();
//唤醒成功执行输出,并唤醒下一个
System.out.print(msg);
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unlock();
}
}
}
}
测试:
public static void main(String[] args) throws InterruptedException {
SyncAwaitSignal awaitSignal = new SyncAwaitSignal(5);
Condition condition_a = awaitSignal.newCondition();
Condition condition_b = awaitSignal.newCondition();
Condition condition_c = awaitSignal.newCondition();
new Thread(()->{
awaitSignal.print("a", condition_a, condition_b);
}).start();
new Thread(()->{
awaitSignal.print("b", condition_b, condition_c);
}).start();
new Thread(()->{
awaitSignal.print("c", condition_c, condition_a);
}).start();
Thread.sleep(1000);
System.out.println("开始");
//运行开始时,给唤醒a
awaitSignal.lock();
try {
condition_a.signal();
} finally {
awaitSignal.unlock();
}
}
class SyncPackUnPack {
private int loopNums;
public SyncPackUnPack(int loopNums) {
this.loopNums = loopNums;
}
public void print(String msg, Thread next){
for (int i = 0; i < loopNums; i++) {
//等待唤醒
LockSupport.park();
//输出并唤醒下一个线程
System.out.print(msg);
LockSupport.unpark(next);
}
}
}
测试:
static Thread t1, t2, t3;
public static void main(String[] args) {
SyncPackUnPack packUnPack = new SyncPackUnPack(5);
t1 = new Thread(()->{
packUnPack.print("a", t2);
});
t2 = new Thread(()->{
packUnPack.print("b", t3);
});
t3 = new Thread(()->{
packUnPack.print("c", t1);
});
t1.start();
t2.start();
t3.start();
LockSupport.unpark(t1);
}