多线程的问题中的经典问题是生产者和消费者的问题,就是如何让线程有序的进行执行,获取CPU执行时间片的过程是随机的,如何能够让线程有序的进行,Java中提供了等待唤醒机制很好的解决了这个问题!

  生产者消费者经典的线程中的问题其实是解决线程中的通讯问题,就是不同种类的线程针对同一资源的操作,这里其实有一张图很好的阐述了这其中的问题:

java等待Process结束 java中等待_线程安全

1 //代码中的实体类
  2 public class Student {
  3     String name;
  4     int age;
  5     boolean flag; // 默认情况是没有数据,如果是true,说明有数据
  6 }
  7 
  8 public class SetThread implements Runnable {
  9 
 10     private Student s;
 11     private int x = 0;
 12 
 13     public SetThread(Student s) {
 14         this.s = s;
 15     }
 16 
 17     @Override
 18     public void run() {
 19         while (true) {
 20             synchronized (s) {
 21                 //判断有没有
 22                 if(s.flag){
 23                     try {
 24                         s.wait(); //t1等着,释放锁
 25                     } catch (InterruptedException e) {
 26                         e.printStackTrace();
 27                     }
 28                 }
 29                 
 30                 if (x % 2 == 0) {
 31                     s.name = "林青霞";
 32                     s.age = 27;
 33                 } else {
 34                     s.name = "刘意";
 35                     s.age = 30;
 36                 }
 37                 x++; //x=1
 38                 
 39                 //修改标记
 40                 s.flag = true;
 41                 //唤醒线程
 42                 s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
 43             }
 44             //t1有,或者t2有
 45         }
 46     }
 47 }
 48 
 49 public class GetThread implements Runnable {
 50     private Student s;
 51 
 52     public GetThread(Student s) {
 53         this.s = s;
 54     }
 55 
 56     @Override
 57     public void run() {
 58         while (true) {
 59             synchronized (s) {
 60                 if(!s.flag){
 61                     try {
 62                         s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
 63                     } catch (InterruptedException e) {
 64                         e.printStackTrace();
 65                     }
 66                 }
 67                 
 68                 System.out.println(s.name + "---" + s.age);
 69                 //林青霞---27
 70                 //刘意---30
 71                 
 72                 //修改标记
 73                 s.flag = false;
 74                 //唤醒线程
 75                 s.notify(); //唤醒t1
 76             }
 77         }
 78     }
 79 }
 80 
 81 /*
 82  * 分析:
 83  *         资源类:Student    
 84  *         设置学生数据:SetThread(生产者)
 85  *         获取学生数据:GetThread(消费者)
 86  *         测试类:StudentDemo
 87  * 
 88  * 问题1:按照思路写代码,发现数据每次都是:null---0
 89  * 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
 90  * 如何实现呢?
 91  *         在外界把这个数据创建出来,通过构造方法传递给其他的类。
 92  * 
 93  * 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
 94  *         A:同一个数据出现多次
 95  *         B:姓名和年龄不匹配
 96  * 原因:
 97  *         A:同一个数据出现多次
 98  *             CPU的一点点时间片的执行权,就足够你执行很多次。
 99  *         B:姓名和年龄不匹配
100  *             线程运行的随机性
101  * 线程安全问题:
102  *         A:是否是多线程环境        是
103  *         B:是否有共享数据        是
104  *         C:是否有多条语句操作共享数据    是
105  * 解决方案:
106  *         加锁。
107  *         注意:
108  *             A:不同种类的线程都要加锁。
109  *             B:不同种类的线程加的锁必须是同一把。
110  * 
111  * 问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出。
112  * 如何实现呢?
113  *         通过Java提供的等待唤醒机制解决。
114  * 
115  * 等待唤醒:
116  *         Object类中提供了三个方法:
117  *             wait():等待
118  *             notify():唤醒单个线程
119  *             notifyAll():唤醒所有线程
120  *         为什么这些方法不定义在Thread类中呢?
121  *             这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
122  *             所以,这些方法必须定义在Object类中。
123  */
124 public class StudentDemo {
125     public static void main(String[] args) {
126         //创建资源
127         Student s = new Student();
128         
129         //设置和获取的类
130         SetThread st = new SetThread(s);
131         GetThread gt = new GetThread(s);
132 
133         //线程类
134         Thread t1 = new Thread(st);
135         Thread t2 = new Thread(gt);
136 
137         //启动线程
138         t1.start();
139         t2.start();
140     }
141 }

线程的状态转换图及常见执行情况:

java等待Process结束 java中等待_java等待Process结束_02

 

上述代码的优化方案:

1 //创建对象的时候实现线程安全
 2 public class Student {
 3     private String name;
 4     private int age;
 5     private boolean flag; // 默认情况是没有数据,如果是true,说明有数据
 6 
 7     public synchronized void set(String name, int age) {
 8         // 如果有数据,就等待
 9         if (this.flag) {
10             try {
11                 this.wait();
12             } catch (InterruptedException e) {
13                 e.printStackTrace();
14             }
15         }
16 
17         // 设置数据
18         this.name = name;
19         this.age = age;
20 
21         // 修改标记
22         this.flag = true;
23         this.notify();
24     }
25 
26     public synchronized void get() {
27         // 如果没有数据,就等待
28         if (!this.flag) {
29             try {
30                 this.wait();
31             } catch (InterruptedException e) {
32                 e.printStackTrace();
33             }
34         }
35 
36         // 获取数据
37         System.out.println(this.name + "---" + this.age);
38 
39         // 修改标记
40         this.flag = false;
41         this.notify();
42     }
43 }
44 
45 public class GetThread implements Runnable {
46     private Student s;
47 
48     public GetThread(Student s) {
49         this.s = s;
50     }
51 
52     @Override
53     public void run() {
54         while (true) {
55             s.get();
56         }
57     }
58 }
59 
60 public class SetThread implements Runnable {
61 
62     private Student s;
63     private int x = 0;
64 
65     public SetThread(Student s) {
66         this.s = s;
67     }
68 
69     @Override
70     public void run() {
71         while (true) {
72             if (x % 2 == 0) {
73                 s.set("林青霞", 27);
74             } else {
75                 s.set("刘意", 30);
76             }
77             x++;
78         }
79     }
80 }
81 
82 public class StudentDemo {
83     public static void main(String[] args) {
84         //创建资源
85         Student s = new Student();
86         
87         //设置和获取的类
88         SetThread st = new SetThread(s);
89         GetThread gt = new GetThread(s);
90 
91         //线程类
92         Thread t1 = new Thread(st);
93         Thread t2 = new Thread(gt);
94 
95         //启动线程
96         t1.start();
97         t2.start();
98     }
99 }