1. 固定运行顺序
1.1 wait notify 版
@Slf4j(topic = "c.Test25")public class Test25 {// 用来同步的对象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;lock.notify();}}, "t2");t1.start();t2.start();}}
输出
16:41:58.698 c.Test25 [t2] - 216:41:58.699 c.Test25 [t1] - 1
1.2 Park Unpark 版
可以看到,实现上很麻烦:
首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该 wait
第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决此问题
最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为『同步对象』上的等待线程可能不止一个
可以使用 LockSupport 类的 park 和 unpark 来简化上面的题目:
@Slf4j(topic = "c.Test26")public class Test26 {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();}}
park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』, 不需要『同步对象』和『运行标记』
输出
16:41:58.698 c.Test26 [t2] - 216:41:58.699 c.Test26 [t1] - 1
2. 交替输出
线程 1 输出 a 3 次,线程 2 输出 b 3 次,线程 3 输出 c 3 次。现在要求输出 abcabcabc 怎么实现
2.1 wait notify 版
public class ExchangePrintTest {public static void main(String[] args) {WaitNotify waitNotify = new WaitNotify(1,3);new Thread(()->{waitNotify.print("a",1,2);}).start();new Thread(()->{waitNotify.print("b",2,3);}).start();new Thread(()->{waitNotify.print("c",3,1);}).start();}}class WaitNotify {/*** 等待标记*/private int flag;/*** 循环次数*/private final int next;public WaitNotify(int flag, int next) {this.flag = flag;this.next = next;}/*** 打印* @param str 打印字符串* @param waitFlag 等待标记* @param nextFlag 下一个标记*/public void print(String str, int waitFlag, int nextFlag){for (int i = 0; i < next; i++) {synchronized (this) {// 当前标记不等于下一步标记while (this.flag != waitFlag) {try {// 该线程等待this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print(str);flag = nextFlag;// 所有线程唤醒this.notifyAll();}}}}
abcabcabc
2.2 Lock 条件变量版
和上面的逻辑是一样的,只是将标识符改为Condition(条件变量),然后使用了Lock锁。
public class ExchangePrintLockTest {public static void main(String[] args) {AwaitSignal awaitSignal = new AwaitSignal(3);Condition a1 = awaitSignal.newCondition();Condition a2 = awaitSignal.newCondition();Condition a3 = awaitSignal.newCondition();new Thread(() -> {awaitSignal.print("a", a1, a2);}).start();new Thread(() -> {awaitSignal.print("b", a2, a3);}).start();new Thread(() -> {awaitSignal.print("c", a3, a1);}).start();// 因为前面的锁,都在睡眠,必须先唤醒a1线程awaitSignal.lock();try {Thread.sleep(1000);System.out.println("开始...");a1.signal();} catch (InterruptedException e) {e.printStackTrace();}finally {awaitSignal.unlock();}}}class AwaitSignal extends ReentrantLock {private final int loopNumber;public AwaitSignal(int loopNumber) {this.loopNumber = loopNumber;}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();}}}}
开始...abcabcabc
进阶写法
public class ExchangePrintLockTestV2 {public static void main(String[] args) {AwaitSignal2 as = new AwaitSignal2(3);as.start(new Thread(() -> {as.print("a");}), new Thread(() -> {as.print("b");}), new Thread(() -> {as.print("c");}), new Thread(() -> {as.print("d");}));}}class AwaitSignal2 extends ReentrantLock {private final int loopNumber;private Map<Thread, Condition[]> map =new ConcurrentHashMap<>();public AwaitSignal2(int loopNumber) {this.loopNumber = loopNumber;}public void start(Thread... threads) {Condition[] temp = new Condition[threads.length];int threadsLength = threads.length;// 创建线程数的标记for (int i = 0; i < threadsLength; i++) {temp[i] = this.newCondition();}// 必须把前面的线程组全部都加入了,才能进行获取线程组中的标记for (int i = 0; i < threadsLength; i++) {// 条件标记Condition current = temp[i];// 下一个标记Condition next;// 2、3、1if (i == threadsLength -1){next = temp[0];} else {next = temp[i + 1];}// 对应的线程拥有的标记map.put(threads[i], new Condition[]{current, next});}// 启动每个线程for (Thread thread : map.keySet()) {thread.start();}try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}// 这里的代码,也是要先唤醒第一个线程,否则进入await就会一直睡眠this.lock();try {// 唤醒第一个线程map.get(threads[0])[0].signal();}finally {this.unlock();}}public void print(String str){for (int i = 0; i < loopNumber; i++) {this.lock();try {Condition[] conditions = map.get(Thread.currentThread());conditions[0].await();System.out.print(str);conditions[1].signal();} catch (InterruptedException e) {e.printStackTrace();} finally {this.unlock();}}}}
2.3 Park Unpark 版
public class Test31 {static Thread t1;static Thread t2;static Thread t3;public static void main(String[] args) {ParkUnpark pu = new ParkUnpark(5);t1 = new Thread(() -> {pu.print("a", t2);});t2 = new Thread(() -> {pu.print("b", t3);});t3 = new Thread(() -> {pu.print("c", t1);});t1.start();t2.start();t3.start();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;}}
