Android(java)学习笔记11:生产者和消费者之等待唤醒机制

1. 首先我们根据梳理我们之前Android(java)学习笔记70中,关于生产者和消费者程序思路:

 2. 下面我们就要重点介绍这个等待唤醒机制

(1)第一步:还是先通过代码体现出等待唤醒机制

下面是测试类:

package cn.itcast_05;

/*
 * 分析:
 *         资源类:Student    
 *         设置学生数据:SetThread(生产者)
 *         获取学生数据:GetThread(消费者)
 *         测试类:StudentDemo
 * 
 * 问题1:按照思路写代码,发现数据每次都是:null---0
 * 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
 * 如何实现呢?
 *         在外界把这个数据创建出来,通过构造方法传递给其他的类。
 * 
 * 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
 *         A:同一个数据出现多次
 *         B:姓名和年龄不匹配
 * 原因:
 *         A:同一个数据出现多次
 *             CPU的一点点时间片的执行权,就足够你执行很多次。
 *         B:姓名和年龄不匹配
 *             线程运行的随机性
 * 线程安全问题:
 *         A:是否是多线程环境        是
 *         B:是否有共享数据        是
 *         C:是否有多条语句操作共享数据    是
 * 解决方案:
 *         加锁。
 *         注意:
 *             A:不同种类的线程都要加锁。
 *             B:不同种类的线程加的锁必须是同一把。
 * 
 * 问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出。
 * 如何实现呢?
 *         通过Java提供的等待唤醒机制解决。
 * 
 * 等待唤醒:
 *         Object类中提供了三个方法:
 *             wait():等待
 *             notify():唤醒单个线程
 *             notifyAll():唤醒所有线程
 *         为什么这些方法不定义在Thread类中呢?
 *             这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
 *             所以,这些方法必须定义在Object类中。
 */
public class StudentDemo {
    public static void main(String[] args) {
        //创建资源
        Student s = new Student();
        
        //设置和获取的类
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);

        //线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        //启动线程
        t1.start();
        t2.start();
    }
}

 下面是生产者线程类:

 1 package cn.itcast_05;
 2 
 3 public class SetThread implements Runnable {
 4 
 5     private Student s;
 6     private int x = 0;
 7 
 8     public SetThread(Student s) {
 9         this.s = s;
10     }
11 
12     @Override
13     public void run() {
14         while (true) {
15             synchronized (s) {
16                 //判断有没有
17                 if(s.flag){
18                     try {
19                         s.wait(); //t1等着,释放锁
20                     } catch (InterruptedException e) {
21                         e.printStackTrace();
22                     }
23                 }
24                 
25                 if (x % 2 == 0) {
26                     s.name = "林青霞";
27                     s.age = 27;
28                 } else {
29                     s.name = "刘意";
30                     s.age = 30;
31                 }
32                 x++; //x=1
33                 
34                 //修改标记
35                 s.flag = true;
36                 //唤醒线程
37                 s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
38             }
39             //t1有,或者t2有
40         }
41     }
42 }

 下面是消费者线程类:

 1 package cn.itcast_05;
 2 
 3 public class GetThread implements Runnable {
 4     private Student s;
 5 
 6     public GetThread(Student s) {
 7         this.s = s;
 8     }
 9 
10     @Override
11     public void run() {
12         while (true) {
13             synchronized (s) {
14                 if(!s.flag){
15                     try {
16                         s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
17                     } catch (InterruptedException e) {
18                         e.printStackTrace();
19                     }
20                 }
21                 
22                 System.out.println(s.name + "---" + s.age);
23                 //林青霞---27
24                 //刘意---30
25                 
26                 //修改标记
27                 s.flag = false;
28                 //唤醒线程
29                 s.notify(); //唤醒t1
30             }
31         }
32     }
33 }

 Student类:

1 package cn.itcast_05;
2 
3 public class Student {
4     String name;
5     int age;
6     boolean flag; // 默认情况是没有数据,默认是false,如果是true,说明有数据
7 }

 

(2)接下来我们对唤醒机制代码进行优化

下面是测试类:

 1 package cn.itcast_07;
 2 
 3 /*
 4  * 分析:
 5  *         资源类:Student    
 6  *         设置学生数据:SetThread(生产者)
 7  *         获取学生数据:GetThread(消费者)
 8  *         测试类:StudentDemo
 9  * 
10  * 问题1:按照思路写代码,发现数据每次都是:null---0
11  * 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
12  * 如何实现呢?
13  *         在外界把这个数据创建出来,通过构造方法传递给其他的类。
14  * 
15  * 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
16  *         A:同一个数据出现多次
17  *         B:姓名和年龄不匹配
18  * 原因:
19  *         A:同一个数据出现多次
20  *             CPU的一点点时间片的执行权,就足够你执行很多次。
21  *         B:姓名和年龄不匹配
22  *             线程运行的随机性
23  * 线程安全问题:
24  *         A:是否是多线程环境        是
25  *         B:是否有共享数据        是
26  *         C:是否有多条语句操作共享数据    是
27  * 解决方案:
28  *         加锁。
29  *         注意:
30  *             A:不同种类的线程都要加锁。
31  *             B:不同种类的线程加的锁必须是同一把。
32  * 
33  * 问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出。
34  * 如何实现呢?
35  *         通过Java提供的等待唤醒机制解决。
36  * 
37  * 等待唤醒:
38  *         Object类中提供了三个方法:
39  *             wait():等待
40  *             notify():唤醒单个线程
41  *             notifyAll():唤醒所有线程
42  *         为什么这些方法不定义在Thread类中呢?
43  *             这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
44  *             所以,这些方法必须定义在Object类中。
45  * 
46  * 最终版代码中47  *         把Student的成员变量给私有的了。
48  *         把设置和获取的操作给封装成了功能,并加了同步。
49  *         设置或者获取的线程里面只需要调用方法即可。
50  */
51 public class StudentDemo {
52     public static void main(String[] args) {
53         //创建资源
54         Student s = new Student();
55         
56         //设置和获取的类
57         SetThread st = new SetThread(s);
58         GetThread gt = new GetThread(s);
59 
60         //线程类
61         Thread t1 = new Thread(st);
62         Thread t2 = new Thread(gt);
63 
64         //启动线程
65         t1.start();
66         t2.start();
67     }
68 }

 生产者线程类:

 1 package cn.itcast_07;
 2 
 3 public class SetThread implements Runnable {
 4 
 5     private Student s;
 6     private int x = 0;
 7 
 8     public SetThread(Student s) {
 9         this.s = s;
10     }
11 
12     @Override
13     public void run() {
14         while (true) {
15             if (x % 2 == 0) {
16                 s.set("林青霞", 27);
17             } else {
18                 s.set("刘意", 30);
19             }
20             x++;
21         }
22     }
23 }

 消费者线程类:

 1 package cn.itcast_07;
 2 
 3 public class GetThread implements Runnable {
 4     private Student s;
 5 
 6     public GetThread(Student s) {
 7         this.s = s;
 8     }
 9 
10     @Override
11     public void run() {
12         while (true) {
13             s.get();
14         }
15     }
16 }

 Student类,同时Student内部封装了Student的两个同步方法(生产Student  和  消费Student)

 1 package cn.itcast_07;
 2 
 3 public class Student {
 4     private String name;
 5     private int age;
 6     private boolean flag; // 默认情况是没有数据,如果是true,说明有数据
 7 
 8     public synchronized void set(String name, int age) {
 9         // 如果有数据,就等待
10         if (this.flag) {
11             try {
12                 this.wait();
13             } catch (InterruptedException e) {
14                 e.printStackTrace();
15             }
16         }
17 
18         // 设置数据
19         this.name = name;
20         this.age = age;
21 
22         // 修改标记
23         this.flag = true;
24         this.notify();
25     }
26 
27     public synchronized void get() {
28         // 如果没有数据,就等待
29         if (!this.flag) {
30             try {
31                 this.wait();
32             } catch (InterruptedException e) {
33                 e.printStackTrace();
34             }
35         }
36 
37         // 获取数据
38         System.out.println(this.name + "---" + this.age);
39 
40         // 修改标记
41         this.flag = false;
42         this.notify();
43     }
44 }

 

3. sleep和wait的区别有:

(1)这两个方法来自不同的类,分别是Thread和Object
(2)最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
(3)wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
   synchronized(x){
        x.notify()
       //或者wait()
   }

(4)sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

原文地址:https://www.cnblogs.com/hebao0514/p/4509997.html