1. 固定运行顺序

比如,必须先 2 后 1 打印

1.1 wait notify 版

  1. @Slf4j(topic = "c.Test25")
  2. public class Test25 {
  3. // 用来同步的对象
  4. static final Object lock = new Object();
  5. // 表示 t2 是否运行过
  6. static boolean t2runned = false;
  7. public static void main(String[] args) {
  8. Thread t1 = new Thread(() -> {
  9. synchronized (lock) {
  10. // 等待t2线程运行
  11. while (!t2runned) {
  12. try {
  13. // t1 先等一会
  14. lock.wait();
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. log.debug("1");
  20. }
  21. }, "t1");
  22. Thread t2 = new Thread(() -> {
  23. synchronized (lock) {
  24. log.debug("2");
  25. t2runned = true;
  26. lock.notify();
  27. }
  28. }, "t2");
  29. t1.start();
  30. t2.start();
  31. }
  32. }

输出

  1. 16:41:58.698 c.Test25 [t2] - 2
  2. 16:41:58.699 c.Test25 [t1] - 1

1.2 Park Unpark 版

可以看到,实现上很麻烦:
首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该 wait
第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决此问题
最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为『同步对象』上的等待线程可能不止一个
可以使用 LockSupport 类的 park 和 unpark 来简化上面的题目:

  1. @Slf4j(topic = "c.Test26")
  2. public class Test26 {
  3. public static void main(String[] args) {
  4. Thread t1 = new Thread(() -> {
  5. // 当没有『许可』时,当前线程暂停运行;
  6. // 有『许可』时,用掉这个『许可』,当前线程恢复运行
  7. LockSupport.park();
  8. log.debug("1");
  9. }, "t1");
  10. t1.start();
  11. new Thread(() -> {
  12. log.debug("2");
  13. // 给线程 t1 发放『许可』(多次连续调用 unpark 只会发放一个『许可』)
  14. LockSupport.unpark(t1);
  15. },"t2").start();
  16. }
  17. }

park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』, 不需要同步对象』和『运行标记
输出

  1. 16:41:58.698 c.Test26 [t2] - 2
  2. 16:41:58.699 c.Test26 [t1] - 1

2. 交替输出

线程 1 输出 a 3 次,线程 2 输出 b 3 次,线程 3 输出 c 3 次。现在要求输出 abcabcabc 怎么实现

2.1 wait notify 版

  1. public class ExchangePrintTest {
  2. public static void main(String[] args) {
  3. WaitNotify waitNotify = new WaitNotify(1,3);
  4. new Thread(()->{
  5. waitNotify.print("a",1,2);
  6. }).start();
  7. new Thread(()->{
  8. waitNotify.print("b",2,3);
  9. }).start();
  10. new Thread(()->{
  11. waitNotify.print("c",3,1);
  12. }).start();
  13. }
  14. }
  15. class WaitNotify {
  16. /**
  17. * 等待标记
  18. */
  19. private int flag;
  20. /**
  21. * 循环次数
  22. */
  23. private final int next;
  24. public WaitNotify(int flag, int next) {
  25. this.flag = flag;
  26. this.next = next;
  27. }
  28. /**
  29. * 打印
  30. * @param str 打印字符串
  31. * @param waitFlag 等待标记
  32. * @param nextFlag 下一个标记
  33. */
  34. public void print(String str, int waitFlag, int nextFlag){
  35. for (int i = 0; i < next; i++) {
  36. synchronized (this) {
  37. // 当前标记不等于下一步标记
  38. while (this.flag != waitFlag) {
  39. try {
  40. // 该线程等待
  41. this.wait();
  42. } catch (InterruptedException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. System.out.print(str);
  47. flag = nextFlag;
  48. // 所有线程唤醒
  49. this.notifyAll();
  50. }
  51. }
  52. }
  53. }
  1. abcabcabc

2.2 Lock 条件变量版

和上面的逻辑是一样的,只是将标识符改为Condition(条件变量),然后使用了Lock锁。

  1. public class ExchangePrintLockTest {
  2. public static void main(String[] args) {
  3. AwaitSignal awaitSignal = new AwaitSignal(3);
  4. Condition a1 = awaitSignal.newCondition();
  5. Condition a2 = awaitSignal.newCondition();
  6. Condition a3 = awaitSignal.newCondition();
  7. new Thread(() -> {
  8. awaitSignal.print("a", a1, a2);
  9. }).start();
  10. new Thread(() -> {
  11. awaitSignal.print("b", a2, a3);
  12. }).start();
  13. new Thread(() -> {
  14. awaitSignal.print("c", a3, a1);
  15. }).start();
  16. // 因为前面的锁,都在睡眠,必须先唤醒a1线程
  17. awaitSignal.lock();
  18. try {
  19. Thread.sleep(1000);
  20. System.out.println("开始...");
  21. a1.signal();
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }finally {
  25. awaitSignal.unlock();
  26. }
  27. }
  28. }
  29. class AwaitSignal extends ReentrantLock {
  30. private final int loopNumber;
  31. public AwaitSignal(int loopNumber) {
  32. this.loopNumber = loopNumber;
  33. }
  34. public void print(String str, Condition current, Condition next) {
  35. for (int i = 0; i < loopNumber; i++) {
  36. lock();
  37. try {
  38. current.await();
  39. System.out.print(str);
  40. next.signal();
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. } finally {
  44. unlock();
  45. }
  46. }
  47. }
  48. }
  1. 开始...
  2. abcabcabc

进阶写法

  1. public class ExchangePrintLockTestV2 {
  2. public static void main(String[] args) {
  3. AwaitSignal2 as = new AwaitSignal2(3);
  4. as.start(new Thread(() -> {
  5. as.print("a");
  6. }), new Thread(() -> {
  7. as.print("b");
  8. }), new Thread(() -> {
  9. as.print("c");
  10. }), new Thread(() -> {
  11. as.print("d");
  12. }));
  13. }
  14. }
  15. class AwaitSignal2 extends ReentrantLock {
  16. private final int loopNumber;
  17. private Map<Thread, Condition[]> map =new ConcurrentHashMap<>();
  18. public AwaitSignal2(int loopNumber) {
  19. this.loopNumber = loopNumber;
  20. }
  21. public void start(Thread... threads) {
  22. Condition[] temp = new Condition[threads.length];
  23. int threadsLength = threads.length;
  24. // 创建线程数的标记
  25. for (int i = 0; i < threadsLength; i++) {
  26. temp[i] = this.newCondition();
  27. }
  28. // 必须把前面的线程组全部都加入了,才能进行获取线程组中的标记
  29. for (int i = 0; i < threadsLength; i++) {
  30. // 条件标记
  31. Condition current = temp[i];
  32. // 下一个标记
  33. Condition next;
  34. // 2、3、1
  35. if (i == threadsLength -1){
  36. next = temp[0];
  37. } else {
  38. next = temp[i + 1];
  39. }
  40. // 对应的线程拥有的标记
  41. map.put(threads[i], new Condition[]{current, next});
  42. }
  43. // 启动每个线程
  44. for (Thread thread : map.keySet()) {
  45. thread.start();
  46. }
  47. try {
  48. Thread.sleep(500);
  49. } catch (InterruptedException e) {
  50. e.printStackTrace();
  51. }
  52. // 这里的代码,也是要先唤醒第一个线程,否则进入await就会一直睡眠
  53. this.lock();
  54. try {
  55. // 唤醒第一个线程
  56. map.get(threads[0])[0].signal();
  57. }finally {
  58. this.unlock();
  59. }
  60. }
  61. public void print(String str){
  62. for (int i = 0; i < loopNumber; i++) {
  63. this.lock();
  64. try {
  65. Condition[] conditions = map.get(Thread.currentThread());
  66. conditions[0].await();
  67. System.out.print(str);
  68. conditions[1].signal();
  69. } catch (InterruptedException e) {
  70. e.printStackTrace();
  71. } finally {
  72. this.unlock();
  73. }
  74. }
  75. }
  76. }

2.3 Park Unpark 版

  1. public class Test31 {
  2. static Thread t1;
  3. static Thread t2;
  4. static Thread t3;
  5. public static void main(String[] args) {
  6. ParkUnpark pu = new ParkUnpark(5);
  7. t1 = new Thread(() -> {
  8. pu.print("a", t2);
  9. });
  10. t2 = new Thread(() -> {
  11. pu.print("b", t3);
  12. });
  13. t3 = new Thread(() -> {
  14. pu.print("c", t1);
  15. });
  16. t1.start();
  17. t2.start();
  18. t3.start();
  19. LockSupport.unpark(t1);
  20. }
  21. }
  22. class ParkUnpark {
  23. public void print(String str, Thread next) {
  24. for (int i = 0; i < loopNumber; i++) {
  25. LockSupport.park();
  26. System.out.print(str);
  27. LockSupport.unpark(next);
  28. }
  29. }
  30. private int loopNumber;
  31. public ParkUnpark(int loopNumber) {
  32. this.loopNumber = loopNumber;
  33. }
  34. }