固定顺序执行
wait && notify
基本思路:
- 线程t1循环等待线程t2执行条件结束
- 线程t2打印2, 然后唤醒wait的线程
package cn.knightzz.partten;
import lombok.extern.slf4j.Slf4j;
/**
* @author 王天赐
* @title: TestAsyncModePrint
* @projectName hm-juc-codes
* @description: 顺序控制之同步打印
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-07-24 13:38
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.TestAsyncModePrint")
public class TestAsyncModePrint {
// 基本思路:
// 1. 线程t1循环等待线程t2执行条件结束
// 2. 线程t2打印2, 然后唤醒wait的线程
private static Object lock = new Object();
private static boolean isFinish = true;
public static void main(String[] args) {
// 固定顺序, 必须先打印2再打印1
Thread t1 = new Thread(() -> {
synchronized (lock) {
while (!isFinish) {
try {
// wait 的时候线程会释放锁
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("print 1");
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
log.debug("print 2");
synchronized (lock) {
// 修改执行标记, 唤醒所有的线程
isFinish = true;
// 唤醒其他线程, 让其他县城进入 EntryList , 等待锁
// t2 执行完也会释放锁
lock.notifyAll();
}
}, "t2");
t2.start();
}
}
Park && unPark
可以看到,实现上很麻烦:
- 首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该 wait
- 第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决 此问题
- 最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为『同步对象』上的等待线程可能不止一个
package cn.knightzz.partten;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
/**
* @author 王天赐
* @title: TestAsyncModePrint
* @projectName hm-juc-codes
* @description: 顺序控制之同步打印
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-07-24 13:38
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.TestAsyncModePrintPark")
public class TestAsyncModePrintPark {
// 基本思路:
// 1. 线程t1循环等待线程t2执行条件结束
// 2. 线程t2打印2, 然后唤醒wait的线程
private static Object lock = new Object();
public static void main(String[] args) {
// 固定顺序, 必须先打印2再打印1
Thread t1 = new Thread(() -> {
// 等待许可证
LockSupport.park();
log.debug("print 1");
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
log.debug("print 2");
// 给t1发放许可证 ,(多次连续调用 unpark 只会发放一个『许可』)
LockSupport.unpark(t1);
}, "t2");
t2.start();
}
}
park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和恢复, 不需要同步对象和运行标记
交替执行
wait && notify
基本思路
- 需要定义一个类用于存储 flag 和 打印的次数
- 假如我们要打印 a b c , 那么 flag 就对应这 1 2 3
- 循环判断 flag 是否满足条件, 如果不满足就一直等待, 满足条件后就打印
- 然后设置 flag = nextFlag , 然后唤醒其他线程
- 所有的线程共享同一个 类对象
代码实现
package cn.knightzz.partten;
import lombok.extern.slf4j.Slf4j;
/**
* @author 王天赐
* @title: TestAlterPrintWait
* @projectName hm-juc-codes
* @description: 使用wait&¬ify交替打印
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-07-24 15:18
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.TestAsyncModePrintPark")
public class TestAlterPrintWait {
// 基本思路 :
// 1. 设置一个整数标记位, 假如线程是3个 t1 => 1, t2 ==> 2 , t3 ==> 3
// 满足条件就打印 , 不满足就循环
// 2. 设置nextFlag, 用于设置下一个打印的标记位
public static void main(String[] args) {
WaitNotify waitNotify = new WaitNotify(1, 5);
new Thread(() -> {
waitNotify.print(1, 2, "a");
}, "t1").start();
new Thread(() -> {
waitNotify.print(2, 3, "b");
}, "t2").start();
new Thread(() -> {
waitNotify.print(3, 1, "c");
}, "t3").start();
}
}
@SuppressWarnings("all")
class WaitNotify {
// 等待标记位
private int flag;
// 设置循环次数
private int loopNumber;
public WaitNotify(int flag, int loopNumber) {
this.flag = flag;
this.loopNumber = loopNumber;
}
/**
* 打印信息 , 要求实现 abcabcabc 这样的效果
*
* @param waitFlag 等待条件标记位
* @param nextFlag 下一个打印的标记位
* @param str 打印的信息
*/
public void print(int waitFlag, int nextFlag, String str) {
for (int i = 0; i < loopNumber; i++) {
synchronized (this) {
// 判断当前标记位是否满足条件
while (waitFlag != flag) {
// 不满足条件就一直等待
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 满足条件就打印信息
System.out.println("str = " + str);
// 设置下一个标记位
this.flag = nextFlag;
// 唤醒其他的线程
this.notifyAll();
}
}
}
}
lock条件变量
基本思路
- 定义 waitSingal 类继承 ReentrantLock
- 通过定义不同的 Condition 对象, 让不同的线程在对应的 WaitSet 里等待.
- 当前线程执行完以后, 释放锁, 然后唤醒下一个线程
代码实现
package cn.knightzz.partten;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 王天赐
* @title: TestAlterPrintLock
* @projectName hm-juc-codes
* @description: 使用Lock实现交替打印
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-07-25 07:32
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.TestAlterPrintLock")
public class TestAlterPrintLock {
public static void main(String[] args) throws InterruptedException {
AwaitSignal awaitSignal = new AwaitSignal(5);
// 创建不同的等待队列
Condition conditionA = awaitSignal.newCondition();
Condition conditionB = awaitSignal.newCondition();
Condition conditionC = awaitSignal.newCondition();
new Thread(() -> {
awaitSignal.print("a", conditionA, conditionB);
}, "t1").start();
new Thread(() -> {
awaitSignal.print("b", conditionB, conditionC);
}, "t2").start();
new Thread(() -> {
awaitSignal.print("c", conditionC, conditionA);
}, "t3").start();
// 睡眠 1s
TimeUnit.SECONDS.sleep(1);
// 主线程先获取锁, 即使其他线程先拿到锁的话, 也会直接await(), 等待时会进入对应的Condition, 然后释放锁
awaitSignal.lock();
log.debug("==============================================");
log.debug("主线程先获取锁, 即使其他线程先拿到锁的话, 也会直接await(), 等待时会进入对应的Condition, 然后释放锁");
log.debug("==============================================");
try {
// 唤醒条件A线程
log.debug("开始 ... ");
conditionA.signal();
}finally {
awaitSignal.unlock();
}
}
}
@SuppressWarnings("all")
@Slf4j(topic = "c.AwaitSignal")
class AwaitSignal extends ReentrantLock {
private int loopNumber;
public AwaitSignal(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String info, Condition current, Condition next) {
for (int i = 0; i < loopNumber; i++) {
// 获取锁, 锁的作用是为了保证原子性, 锁获取的周期内, 代码可以完整的执行 , 而不会进行线程上下文的切换
this.lock();
log.debug("成功获取锁!");
try {
// 当前线程等待
log.debug("当前线程等待!");
current.await();
log.debug("等待结束打印 info!");
// 等待结束打印 info
log.debug(" info = {}", info);
// 唤醒下一个Condition的线程
log.debug("唤醒下一个Condition的线程!");
next.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 释放锁
this.unlock();
}
}
}
}
park && unpark
基本思路
- 思路很简单, 定义一个 SyncPark 类 , 定义一个 Thread 数组对象threads, 然后用于存储线程
- 获取下一个线程的时候, 就遍历 threads , 然后使用
LockSupport.unpark(next);
代码实现
package cn.knightzz.partten;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
/**
* @author 王天赐
* @title: TestAlterPrintPark
* @projectName hm-juc-codes
* @description: 使用Park&&UnPark交替打印
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-07-25 09:05
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.TestAlterPrintPark")
public class TestAlterPrintPark {
public static void main(String[] args) {
SyncPark syncPark = new SyncPark(5);
Thread t1 = new Thread(() -> {
syncPark.print("a");
}, "t1");
Thread t2 = new Thread(() -> {
syncPark.print("b");
}, "t2");
Thread t3 = new Thread(() -> {
syncPark.print("c");
}, "t3");
syncPark.setThreads(t1, t2, t3);
syncPark.start();
}
}
@Slf4j(topic = "c.SyncPark")
@SuppressWarnings("all")
class SyncPark {
private int loopNumber;
private Thread[] threads;
public SyncPark(int loopNumber) {
this.loopNumber = loopNumber;
}
public void setThreads(Thread... threads) {
this.threads = threads;
}
public void print(String info) {
for (int i = 0; i < loopNumber; i++) {
// 获取锁
LockSupport.park();
// 打印
log.debug("info = {} ", info);
// 释放锁
Thread next = nextThread();
LockSupport.unpark(next);
}
}
public Thread nextThread() {
// 获取当前线程
Thread current = Thread.currentThread();
int index = 0;
// 遍历线程数组, 找到当前线程
for (int i = 0; i < threads.length; i++) {
if (threads[i] == current) {
index = i;
break;
}
}
if (index < threads.length - 1) {
return threads[index + 1];
} else {
return threads[0];
}
}
public void start() {
for (Thread thread : threads) {
thread.start();
}
// 释放凭证
LockSupport.unpark(threads[0]);
}
}