固定运行顺序

比如,先打印2再打印1

Wait-Notify版本

  1. public class TestShunXu {
  2. static final Object lock = new Object();//锁对象
  3. //表示t2是否运行过
  4. static boolean t2runned = false;
  5. public static void main(String[] args) {
  6. Thread t1 = new Thread(()->{
  7. synchronized (lock){
  8. while(!t2runned){
  9. try {
  10. lock.wait();
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. System.out.println("1");
  16. }
  17. },"t1");
  18. Thread t2 = new Thread(()->{
  19. synchronized (lock){
  20. t2runned=true;
  21. lock.notify();
  22. }
  23. },"t2");
  24. }
  25. }

实际上线程1中wait等待的条件就是线程2是否运行,在编写线程2同步代码块中的代码时,逻辑为:进入同步代码块即已经拿到了锁,则代表线程2正在访问,即将t2ruuner置为true即可,如果线程1处于等待状态notify唤醒,如果线程1还没获得锁处于阻塞状态(没能进入同步代码块中),线程2的notify方法即相当于无效,不会产生问题。

Park-Unpark版本

  1. import java.util.concurrent.locks.Lock;
  2. import java.util.concurrent.locks.LockSupport;
  3. public class TestShunXu {
  4. public static void main(String[] args) {
  5. Thread t1 = new Thread(()->{
  6. LockSupport.park();//等待
  7. System.out.println("1");
  8. },"t1");
  9. Thread t2 = new Thread(()->{
  10. System.out.println("2");
  11. LockSupport.unpark(t1);
  12. },"t2");
  13. t1.start();
  14. t2.start();
  15. }
  16. }

最简单的版本,但是也需注意,与同步块控制不同,这里未加锁所以两个线程实际是并行运行,但也要考虑语句的执行先后。
例如t1先调用park则会处于等待状态,线程2执行完毕后再回来唤醒则没有问题;
例如t2线程先调用unpark则也没有问题,这里参考park-unpark原理;

交替输出(核心例子,面向对象的多线程编程思想)

问题描述:三个线程,交替输出abc,一共输出5次。
输出效果:abcabcabcabcabc

Wait-Notify版本

  1. public class TestTurnPrint {
  2. public static void main(String[] args) {
  3. WaitNotify wn= new WaitNotify(1,5);//多个线程共享这一份对象
  4. new Thread(()->{
  5. try {
  6. wn.print("a",1,2);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. }).start();
  11. new Thread(()->{
  12. try {
  13. wn.print("b",2,3);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }).start();
  18. new Thread(()->{
  19. try {
  20. wn.print("c",3,1);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }).start();
  25. }
  26. }
  27. /*
  28. 输出内容 等待标记 下一个标记
  29. a 1 2
  30. b 2 3
  31. c 3 1
  32. */
  33. class WaitNotify{
  34. //等待标记
  35. private int flag;
  36. //循环次数
  37. private int loopNumber;
  38. //构造方法
  39. public WaitNotify(int flag,int loopNumber){
  40. this.flag=flag;
  41. this.loopNumber=loopNumber;
  42. }
  43. //核心方法-打印
  44. public void print(String str,int waitFlag,int nextFlag) throws InterruptedException {
  45. for(int i=0;i<loopNumber;i++){
  46. synchronized (this){
  47. while(flag!=waitFlag){
  48. this.wait();
  49. }
  50. System.out.println(str);
  51. flag=nextFlag;
  52. this.notifyAll();
  53. }
  54. }
  55. }
  56. }

Await-Signal版本

  1. import javax.sound.midi.Soundbank;
  2. import java.util.concurrent.locks.Condition;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. public class TesAwaitAndSignal {
  5. public static void main(String[] args) throws InterruptedException {
  6. AwaitSignal awaitSignal = new AwaitSignal(5);//创建ReentrantLock对象
  7. Condition a =awaitSignal.newCondition();
  8. Condition b = awaitSignal.newCondition();
  9. Condition c = awaitSignal.newCondition();
  10. new Thread(()->{
  11. awaitSignal.print("a",a,b);
  12. }).start();
  13. new Thread(()->{
  14. awaitSignal.print("b",b,c);
  15. }).start();
  16. new Thread(()->{
  17. awaitSignal.print("c",c,a);
  18. }).start();
  19. //因为三个线程都会进入到【WAITING】状态中,所以需要手动唤醒a线程
  20. Thread.sleep(1000);
  21. System.out.println("开始运行......");
  22. awaitSignal.lock();
  23. try{
  24. a.signal();//唤醒a休息室中的中线程,因为只有一个线程,所以是精准唤醒
  25. }finally {
  26. awaitSignal.unlock();
  27. }
  28. }
  29. }
  30. class AwaitSignal extends ReentrantLock{
  31. private int loopNumber;
  32. public AwaitSignal(int loopNumber){
  33. this.loopNumber=loopNumber;
  34. }
  35. //参数1打印内容,参数2进入哪一间休息室,参数3进入下一间休息室
  36. public void print(String str, Condition current,Condition next){
  37. for(int i=0;i<loopNumber;i++){
  38. this.lock();//上锁
  39. try {
  40. current.await();
  41. System.out.println(str);
  42. next.signal();//唤醒下一间休息室等待的线程
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. } finally {
  46. this.unlock();
  47. }
  48. }
  49. }
  50. }

这个版本比较清晰,线程均执行await方法进入等待状态,当线程被唤醒的时候,执行代码输出字符并将下一个线程唤醒,循环五次即可实现线程的循环输出,本例中也用到了多条件变量,所以能够精准唤醒,没有了虚假唤醒所以不需要在await方法外加入while循环,这是与wait-notify方法的区别。

Park-Unpark版本

  1. import java.util.concurrent.locks.Lock;
  2. import java.util.concurrent.locks.LockSupport;
  3. public class TestParkAndUnpark {
  4. static Thread t1;
  5. static Thread t2;;
  6. static Thread t3;
  7. public static void main(String[] args) {
  8. ParkUnpark pu =new ParkUnpark(5);
  9. t1 = new Thread(()->{
  10. pu.print("a",t2);
  11. });
  12. t2 = new Thread(()->{
  13. pu.print("b",t3);
  14. });
  15. t3 = new Thread(()->{
  16. pu.print("c",t1);
  17. });
  18. t1.start();
  19. t2.start();
  20. t3.start();
  21. //主线程启动
  22. LockSupport.unpark(t1);
  23. }
  24. }
  25. class ParkUnpark{
  26. private int loopNumber;
  27. public ParkUnpark(int loopNumber) {
  28. this.loopNumber = loopNumber;
  29. }
  30. public void print(String str,Thread next){
  31. for (int i=0;i<loopNumber;i++){
  32. LockSupport.park();//进入线程则使得线程停下来
  33. System.out.println(str);
  34. LockSupport.unpark(next);
  35. }
  36. }
  37. }

park&unpark方法没有加锁的限制,没有同步代码块,与前两种方法的思路一致,首先三个线程均进入等待状态,由唤醒顺序来决定线程的执行顺序。