定义
即 Guarded Suspension,用在一个线程等待另一个线程的执行结果
要点
- 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject
- 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
- JDK 中,join 的实现、Future 的实现,采用的就是此模式
- 因为要等待另一方的结果,因此归类到同步模式
基本实现
1. public class GuardedObject{ 2. private Object response; 3. private final Object lock=new Object(); 4. public Object get(){ 5. synchronized (lock){ 6. //条件不满足进行等待 7. while (response==null){ 8. try { 9. lock.wait(); 10. } catch (InterruptedException e) { 11. e.printStackTrace(); 12. } 13. } 14. return response; 15. } 16. } 17. 18. public void complete(Object response){ 19. synchronized (lock){ 20. this.response=response; 21. lock.notifyAll(); 22. } 23. } 24. 25. }
1. public static void main(String[] args) { 2. ThreadText GuardedObject= new GuardedObject(); 3. new Thread(()->{ 4. String response="结果"; 5. System.out.println("正在加载"); 6. try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } 7. GuardedObject.complete(response); 8. }).start(); 9. System.out.println("wait...."); 10. //主线程进行等待 11. Object o = GuardedObject.get(); 12. System.out.println("输出结果===》"+o); 13. }
带超时版 GuardedObject
1. public class GuardedObject { 2. private Object response; 3. private final Object lock=new Object(); 4. public Object get(Long millis){ 5. synchronized (lock){ 6. //记录最初时间 7. long begin=System.currentTimeMillis(); 8. //已经经历时间 9. long timePassed=0; 10. //条件不满足进行等待 11. while (response==null){ 12. long waitTime=millis-timePassed; 13. if(waitTime<=0){ 14. break; 15. } 16. try { 17. lock.wait(); 18. } catch (InterruptedException e) { 19. e.printStackTrace(); 20. } 21. timePassed= System.currentTimeMillis()- begin; 22. } 23. return response; 24. } 25. } 26. 27. public void complete(Object response){ 28. synchronized (lock){ 29. this.response=response; 30. lock.notifyAll(); 31. } 32. } 33. 34. }
测试代码
1. public static void main(String[] args) { 2. GuardedObject GuardedObject= new GuardedObject(); 3. new Thread(()->{ 4. String response="结果"; 5. System.out.println("正在加载"); 6. GuardedObject.complete(null); 7. try { Thread.sleep(102); } catch (InterruptedException e) { e.printStackTrace(); } 8. GuardedObject.complete(null); 9. try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } 10. GuardedObject.complete(response); 11. }).start(); 12. System.out.println("wait...."); 13. //主线程进行等待 14. Object o = GuardedObject.get(100); 15. System.out.println("输出结果===》"+o); 16. 17. }
多任务版 GuardedObject
图中 Futures 就好比居民楼一层的信箱(每个信箱有房间编号),左侧的 t0,t2,t4 就好比等待邮件的居民,右 侧的 t1,t3,t5 就好比邮递员
如果需要在多个类之间使用 GuardedObject 对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类, 这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理
新增 id 用来标识 Guarded Object
1. public class GuardedObject { 2. //标识GuardedObject 3. private int id; 4. 5. public int getId() { 6. return id; 7. } 8. 9. public void setId(int id) { 10. this.id = id; 11. } 12. 13. private Object response; 14. 15. //获取结果 16. public Object get(long timeout) { 17. synchronized (this) { 18. //开始时间 19. long begin = System.currentTimeMillis(); 20. //经历的时间 21. long passedTime = 0; 22. while (response == null) { 23. long waitTime = timeout - passedTime; 24. if (waitTime <= 0) { 25. break; 26. } 27. try { 28. this.wait(); 29. } catch (InterruptedException e) { 30. e.printStackTrace(); 31. } 32. //求得经历的时间 33. passedTime = System.currentTimeMillis() - begin; 34. } 35. return response; 36. } 37. } 38. 39. //产生结果 40. public void complete(Object response) { 41. synchronized (this) { 42. //给结果进行赋值 43. this.response = response; 44. this.notifyAll(); 45. } 46. } 47. 48. public GuardedObject(int id) { 49. this.id = id; 50. } 51. 52. 53. }
中间解耦类
1. class MailBoxes { 2. private static Map<Integer, GuardedObject> boxes = new HashMap<>(); 3. private static int id = 1; 4. 5. //产生唯一id 6. private static synchronized int generateId() { 7. return id++; 8. } 9. 10. public static GuardedObject getGuardedObject(int id) { 11. return boxes.remove(id); 12. } 13. 14. 15. public static GuardedObject createGuardedObject() { 16. GuardedObject guardedObject = new GuardedObject(generateId()); 17. boxes.put(guardedObject.getId(), guardedObject); 18. return guardedObject; 19. } 20. 21. public static Set<Integer> getIds(){ 22. return boxes.keySet(); 23. } 24. 25. }
业务相关类
1. class People extends Thread{ 2. public void run() { 3. GuardedObject guardedObject = MailBoxes.createGuardedObject(); 4. System.out.println("开始收信id==>"+guardedObject.getId()); 5. Object mail = guardedObject.get(5000); 6. System.out.println("收到信id==>"+guardedObject.getId()+" 内容==>"+mail); 7. 8. } 9. } 10. 11. 12. class Postman extends Thread{ 13. private int id; 14. private String mail; 15. public Postman(int id,String mail){ 16. this.id=id; 17. this.mail=mail; 18. } 19. 20. public void run() { 21. GuardedObject guardedObject = MailBoxes.getGuardedObject(id); 22. System.out.println("送信id==>"+id+" 内容==>"+mail); 23. guardedObject.complete(mail); 24. } 25. }
测试代码
1. public static void main(String[] args) { 2. for (int i = 0; i < 3; i++) { 3. new People().start(); 4. try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } 5. } 6. 7. for (Integer id:MailBoxes.getIds()){ 8. new Postman(id,"内容"+id).start(); 9. 10. } 11. }
测试结果:
开始收信id==>1
开始收信id==>2
开始收信id==>3
送信id==>1 内容==>内容1
送信id==>2 内容==>内容2
送信id==>3 内容==>内容3
收到信id==>1 内容==>内容1
收到信id==>2 内容==>内容2
收到信id==>3 内容==>内容3
总结
保护性暂停(Guarded Suspension)是一种线程间的同步机制,它解决了等待-通知模式中的等待超时和虚假唤醒问题。在保护性暂停模式中,一个线程在等待某个特定条件的满足时,会通过循环的方式不断检查这个条件,同时在条件不满足时通过wait()方法来释放占用的锁,并进入等待状态;当条件被满足时,相应的其他线程会通过notify()或notifyAll()方法来唤醒正在等待的线程。
具体来说,保护性暂停包含以下几个方面:
- 条件判断:在保护性暂停中,线程在等待前会先进行一次条件判断,以确定是否需要进入等待状态,从而避免不必要的等待和唤醒。通常情况下,在条件不满足时线程会通过wait()方法进入等待状态,而在条件满足时则继续执行。
- 执行顺序:在保护性暂停中,线程之间的执行顺序是不可控的。例如,在一个生产者-消费者模型中,当生产者线程唤醒消费者线程时,不能保证其立即执行,也不能保证消费者线程的执行顺序。
- 同步机制:在保护性暂停中,需要使用同步机制来确保线程之间的可见性和互斥性。通常情况下,使用synchronized关键字来保护共享资源,在保证线程之间的可见性和互斥性的同时,避免了死锁和饥饿等问题。
- 等待超时:为了避免线程一直等待而导致程序不响应,保护性暂停通常会使用等待超时机制。即在等待一定时间后,如果条件仍然不满足,则主动放弃等待并返回一个默认值,从而避免阻塞线程。
总体来说,保护性暂停是一种有效的线程同步机制,它可以在多线程环境下保证数据的正确性和程序的健壮性。但是,在使用保护性暂停时需要注意线程之间的协作和同步问题,特别是在条件判断和等待超时等方面。