多线程的问题中的经典问题是生产者和消费者的问题,就是如何让线程有序的进行执行,获取CPU执行时间片的过程是随机的,如何能够让线程有序的进行,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 }
线程的状态转换图及常见执行情况:
上述代码的优化方案:
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 }