定义:

即Guarded Suspension、用在一个线程等待另一个线程的执行结果

  • 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject
  • 如果有结果不断从一个线程到另一个线程可以使用消息队列(生产者/消费者)
  • JDK中,join的实现、Future的实现,采用就是此模式
  • 因为要等待另一方的结果,因此归类到同步模式

image.png

实现:

  1. public class GuardedObject {
  2. //结果
  3. private Object response;
  4. //获取结果
  5. public Object get() throws InterruptedException {
  6. synchronized (this){
  7. //还没有结果
  8. while(response==null){
  9. this.wait();
  10. }
  11. }
  12. return response;
  13. }
  14. public void complete(Object response){
  15. synchronized (this){
  16. //给结果成员变量赋值
  17. this.response=response;
  18. this.notifyAll();
  19. }
  20. }
  21. }

GuardedObject类实现细节如上,一个成员变量用来存放存储结果,再自定义两个方法更改变量值。

具体实例:线程1等待线程2下载完毕内容,注意object相当于共享变量,所以类中方法要加入synchronized代码块。

  1. import javax.imageio.IIOException;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.net.HttpURLConnection;
  6. import java.net.URL;
  7. import java.nio.charset.StandardCharsets;
  8. import java.util.ArrayList;
  9. import java.util.List;
  10. public class GuardedObject {
  11. //结果
  12. private Object response;
  13. //获取结果
  14. public Object get() throws InterruptedException {
  15. synchronized (this){
  16. //还没有结果
  17. while(response==null){
  18. this.wait();
  19. }
  20. }
  21. return response;
  22. }
  23. public void complete(Object response){
  24. synchronized (this){
  25. //给结果成员变量赋值
  26. this.response=response;
  27. this.notifyAll();
  28. }
  29. }
  30. }
  31. class Test20{
  32. public static void main(String[] args) {
  33. GuardedObject object = new GuardedObject();
  34. new Thread(()->{
  35. //等待结果
  36. System.out.println("等待结果");
  37. List<String> list = null;
  38. try {
  39. list = (List<String>) object.get();
  40. } catch (InterruptedException e) {
  41. e.printStackTrace();
  42. }
  43. System.out.println(list.size());
  44. },"t1").start();
  45. new Thread(()->{
  46. System.out.println("执行下载");
  47. List<String> list = null;
  48. try {
  49. list = Downloader.download();
  50. } catch (IOException e) {
  51. e.printStackTrace();
  52. }
  53. object.complete(list);
  54. },"t2").start();
  55. }
  56. }
  57. class Downloader{
  58. public static List<String> download() throws IOException{
  59. HttpURLConnection conn = (HttpURLConnection) new URL("www.baidu.com").openConnection();
  60. List<String> lines =new ArrayList<>();
  61. try(BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(),StandardCharsets.UTF_8))){
  62. String line;
  63. while ((line=reader.readLine())!=null){
  64. lines.add(line);
  65. }
  66. }
  67. return lines;
  68. }
  69. }

此种写法的好处,以往的join()方法,线程1需要用等待线程2完全执行结束才能拿到结果,但这是没必要的,完全可以在线程2生成结果后使得线程2继续执行。
所以这种带wait-notify的形式称为“保护性暂停”

扩展-增加超时:

当线程wait时候条件不得到满足,按照while-wait(空参)的写法,就会陷入空等,只有条件满足时线程才可以继续执行下去。所以要使用带参数的wait方法,控制等待的时间,如果时间满足则不再等待。
代码如下:

  1. //获取结果
  2. public Object get(int timeout) throws InterruptedException {
  3. synchronized (this){
  4. long begin = System.currentTimeMillis();
  5. //定义经历的时间
  6. long passedTime = 0;
  7. while(response==null){
  8. long waitTime = timeout-passedTime;
  9. //经历的时间超过了最大等待时间,退出循环
  10. if(waitTime<=0){
  11. break;//跳出循环,不再等待
  12. }
  13. this.wait(waitTime);
  14. passedTime = System.currentTimeMillis()-begin;//获得经历时间
  15. }
  16. }
  17. return response;
  18. }

设置一个passedTime,记录循环中等待的时间,一旦超时则通过break跳出循环,线程执行。但是一定要注意虚假唤醒的情况,如果wait位置被notifyAll虚假唤醒,一定要将之前等待的时间扣除,是根据waitTime进一步判断,而不是根据timeout值判断,这里一定要注意。