李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
23.同步模式之顺序控制
Leefs
2022-10-26 PM
1193℃
0条
[TOC] ### 一、固定运行顺序 > 题目:有两个线程分别输出1和2,要求输出结果必须先2后1打印 #### 1.1 wait notify 版 ```java import lombok.extern.slf4j.Slf4j; /** * @author lilinchao * @date 2022-10-26 * @description 固定运行顺序 wait notify实现 * 必须先2后1打印 **/ @Slf4j(topic = "c.Test01") public class Test01 { // 用来同步的对象 static final Object lock = new Object(); //表示 t2 是否运行过 static boolean t2runned = false; public static void main(String[] args) { Thread t1 = new Thread(() -> { synchronized (lock){ // 如果 t2 没有执行过 while (!t2runned){ try { // t1 先等一会 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("1"); } },"t1"); Thread t2 = new Thread(() -> { synchronized (lock) { log.debug("2"); // 修改运行标记 t2runned = true; // 通知 obj 上等待的线程(可能有多个,因此需要用 notifyAll) lock.notifyAll(); } },"t2"); t1.start(); t2.start(); } } ``` **运行结果** ``` 20:54:47.396 c.Test01 [t2] - 2 20:54:47.398 c.Test01 [t1] - 1 ``` 可以看到,实现上很麻烦: + 首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该 wait; + 第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决 此问题; + 最后,唤醒对象上的 wait 线程需要使用 `notifyAll`,因为『同步对象』上的等待线程可能不止一个。 #### 1.2 await/signal版本实现 ```java import lombok.extern.slf4j.Slf4j; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * @author lilinchao * @date 2022-10-26 * @description 固定运行顺序 - await/signal版本实现 **/ @Slf4j(topic = "c.Test06") public class Test06 { public static final ReentrantLock lock = new ReentrantLock(); public static Condition condition = lock.newCondition(); // t2线程释放执行过 public static boolean t2Runned = false; public static void main(String[] args) { Thread t1 = new Thread(() -> { lock.lock(); try { // 临界区 while (!t2Runned) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("1"); } finally { lock.unlock(); } }, "t1"); Thread t2 = new Thread(() -> { lock.lock(); try { log.debug("2"); t2Runned = true; condition.signal(); } finally { lock.unlock(); } }, "t2"); t1.start(); t2.start(); } } ``` **运行结果** ``` 21:38:38.463 c.Test06 [t2] - 2 21:38:38.464 c.Test06 [t1] - 1 ``` #### 1.3 Park Unpark 版 可以使用 `LockSupport` 类的 park 和 `unpark` 来简化上面的题目: ```java import lombok.extern.slf4j.Slf4j; import java.util.concurrent.locks.LockSupport; /** * @author lilinchao * @date 2022-10-26 * @description 固定运行顺序 Park Unpark实现 **/ @Slf4j(topic = "c.Test02") public class Test02 { public static void main(String[] args) { Thread t1 = new Thread(() -> { // 当没有『许可』时,当前线程暂停运行;有『许可』时,用掉这个『许可』,当前线程恢复运行 LockSupport.park(); log.debug("1"); }, "t1"); t1.start(); new Thread(() -> { log.debug("2"); // 给线程 t1 发放『许可』(多次连续调用 unpark 只会发放一个『许可』) LockSupport.unpark(t1); },"t2").start(); } } ``` **运行结果** ``` 21:33:28.193 c.Test02 [t2] - 2 21:33:28.195 c.Test02 [t1] - 1 ``` park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』, 不需要『同步对象』和『运行标记』 ### 二、交替输出 > 题目:线程1 输出 a 5次, 线程2 输出 b 5次, 线程3 输出 c 5次。现在要求输出 abcabcabcabcabcab #### 2.1 wait/notify版本 通过设置等待标记 flag 来记录当前拥有锁的是哪个线程, 设置下一个标记,来记录下一个唤醒的该是哪个线程。 ```java /** * @author lilinchao * @date 2022-10-26 * @description 交替输出 wait notify实现 **/ public class Test03 { public static void main(String[] args) { WaitNotify waitNotify = new WaitNotify(1,5); new Thread(() -> { waitNotify.print("a",1,2); }).start(); new Thread(() -> { waitNotify.print("b",2,3); }).start(); new Thread(() -> { waitNotify.print("c",3,1); }).start(); } } /* 输出内容 等待标记 下一个标记 a 1 2 b 2 3 c 3 1 */ class WaitNotify { // 打印 public void print(String str,int waitFlag,int nextFlag){ for (int i = 0; i < loopNumber; i++){ synchronized (this) { while (flag != waitFlag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print(str); // 修改等待标记 让下一个线程打印 flag = nextFlag; // 唤醒等待线程 this.notifyAll(); } } } // 等待标记 private int flag; // 循环次数 private int loopNumber; public WaitNotify(int flag, int loopNumber) { this.flag = flag; this.loopNumber = loopNumber; } } ``` **运行结果** ``` abcabcabcabcabc ``` #### 2.2 await/signal版本 由于 `ReentrantLock` 具有多个条件变量的特性,即多个 `WaitSet` 休息室,所以可以通过设置让其进入不同的休息室休息来实现输出当前线程的字符串,并唤醒下一个休息室中的线程。这种方法存在虚假唤醒的情况,因为没有做 while 判断,但此例中每个休息室中只有一个线程,因此不存在虚假唤醒的情况。 ```java import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import static com.lilinchao.thread.utils.Sleeper.sleep; /** * @author lilinchao * @date 2022-10-26 交替输出 await/signal * @description **/ public class Test04 { public static void main(String[] args) { AwaitSignal awaitSignal = new AwaitSignal(5); Condition a = awaitSignal.newCondition(); Condition b = awaitSignal.newCondition(); Condition c = awaitSignal.newCondition(); new Thread(() -> { awaitSignal.print("a",a,b); }).start(); new Thread(() -> { awaitSignal.print("b",b,c); }).start(); new Thread(() -> { awaitSignal.print("c",c,a); }).start(); sleep(1); awaitSignal.lock(); try { System.out.println("开始..."); a.signal(); } finally { awaitSignal.unlock(); } } } class AwaitSignal extends ReentrantLock { private int loopNumber; public AwaitSignal(int loopNumber) { this.loopNumber = loopNumber; } /** * 打印 * @param str 打印内容 * @param current 进入哪一间休息室 * @param next 下一间休息室 */ public void print(String str, Condition current,Condition next) { for (int i = 0; i < loopNumber; i++){ lock(); try { current.await(); System.out.print(str); next.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { unlock(); } } } } ``` **运行结果** ``` 开始... abcabcabcabcabc ``` #### 2.3 park/unpark实现 park和unpark没有对象锁的概念了,停止和恢复线程的运行都是以线程自身为单位的,所以实现更为简单。 ```java import lombok.extern.slf4j.Slf4j; import java.util.concurrent.locks.LockSupport; /** * @author lilinchao * @date 2022-10-26 * @description 交替输出 Park/Unpark **/ @Slf4j(topic = "c.Test05") public class Test05 { static Thread t1; static Thread t2; static Thread t3; public static void main(String[] args) { ParkUnpark unpark = new ParkUnpark(5); t1 = new Thread(() -> { unpark.print("a", t2); }); t2 = new Thread(() -> { unpark.print("b", t3); }); t3 = new Thread(() -> { unpark.print("c", t1); }); t1.start(); t2.start(); t3.start(); //主线程先唤醒t1 LockSupport.unpark(t1); } } class ParkUnpark { public void print(String str,Thread next){ for (int i = 0; i < loopNumber; i++){ //当前线程先暂停 LockSupport.park(); System.out.print(str); //唤醒下一个线程 LockSupport.unpark(next); } } private int loopNumber; public ParkUnpark(int loopNumber) { this.loopNumber = loopNumber; } } ``` **运行结果** ``` abcabcabcabcabc ```
标签:
并发编程
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2524.html
上一篇
22.并发编程之ReentrantLock简介
下一篇
24.并发编程之可见性介绍
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
nginx
Spark
BurpSuite
Shiro
Eclipse
Yarn
数学
RSA加解密
MySQL
FileBeat
pytorch
并发编程
Spark RDD
数据结构
FastDFS
Jquery
MyBatisX
SpringCloudAlibaba
Java阻塞队列
哈希表
Golang基础
MyBatis-Plus
Java
链表
栈
设计模式
Elastisearch
ajax
Java编程思想
工具
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭