系统化学习多线程(二)-线程同步-等待-通知

1.大纲

-------------------------学前必读----------------------------------

学习不能快速成功,但一定可以快速入门
整体课程思路:
1.实践为主,理论化偏少
2.课程笔记有完整的案例和代码,(为了学习效率)再开始之前我会简单粗暴的介绍知识点案例思路,
有基础的同学听了之后可以直接结合笔记写代码,
如果没听懂再向下看视频,我会手把手编写代码和演示测试结果;
3.重要提示,学编程和学游泳一样,多实践学习效率才高,理解才透彻;
4.编码功底差的建议每个案例代码写三遍,至于为什么...<<卖油翁>>...老祖宗的智慧

-------------------------------------------------------------------------

2.线程同步

1.synchronized锁住的是括号里的对象,而不是代码。
2.常用锁对象(this,字节码)
  1. 当synchronized (TicketThread.class),程序执行正常,只用一份字节码,谁拥有字节码谁就有执行权
  2. synchronized (lockObject),程序执行正常,因为整个应用上下文只有一个lockObject对象,谁拥有lockObject对象,谁就有执行权
  3. synchronized (this),有重复售票的现象,this代表当前对象,拥有当前对象就拥有执行权,而在时间调用中我们创建了多个TicketThread对象,因此锁是无效的;
  4. synchronized (num),有重复售票现象,因为num是变动的,如果有2个线程都是num=99那么只能有其中一个线程有执行权,
       但是如果num数不一样可以同时执行,这里为后面讲出售不同的班次的车票时提高锁的效率埋下伏笔

3.使用同步方法是,不需要手动添加同步监听对象,
  如果是实例方法,那么默认的同步监听对象就是this,
  如果是静态方法,默认的同步监听对象是类的字节码对象;
4.注意,当使用字节码锁时,使用了synchronized (TicketThread.class) 的地方都会被锁住,即使在不同的方法内;

5.ReentrantLock提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作,性能更高。

6.如果要对不同的数据加锁,应该怎么办,比如购票业务中只对同一班次的车票加锁.

需求:编写模拟售票程序,验证上面的结论

测试对象

 1 package com.wfd360.thread;
 2 
 3 import com.wfd360.thread.demo05Syn.TicketThread;
 4 import org.junit.Test;
 5 
 6 /**
 7  * @author 姿势帝-博客园
 8  * @address https://www.cnblogs.com/newAndHui/
 9  * @WeChat 851298348
10  * @create 05/05 10:53
11  * @description 线程同步
12  * 1.同步代码块
13  * synchronized锁住的是括号里的对象,而不是代码。
14  * 2.同步方法
15  * 使用同步方法是,不需要手动添加同步监听对象,
16  * 如果是实例方法,那么默认的同步监听对象就是this,
17  * 如果是静态方法,默认的同步监听对象是类的字节码对象;
18  * 3.同步锁-ReentrantLock
19  */
20 public class Test08Syn {
21     /**
22      * 测试:
23      */
24     @Test
25     public void testTicketThread() throws InterruptedException {
26         System.out.println("---test start-------");
27         TicketThread thread1 = new TicketThread();
28         thread1.setName("窗口1");
29         TicketThread thread2 = new TicketThread();
30         thread2.setName("窗口2");
31         TicketThread thread3 = new TicketThread();
32         thread3.setName("窗口3");
33         // 开启线程
34         thread1.start();
35         thread2.start();
36         thread3.start();
37         Thread.sleep(10);
38         //测试普通方法能否被调用
39         //thread1.method1();
40         System.out.println("==========等待售票============");
41         Thread.sleep(10 * 1000);
42         System.out.println("---test end-------");
43     }
44 }

线程对象

  1 package com.wfd360.thread.demo05Syn;
  2 
  3 import java.util.concurrent.locks.ReentrantLock;
  4 
  5 /**
  6  * @author 姿势帝-博客园
  7  * @address https://www.cnblogs.com/newAndHui/
  8  * @WeChat 851298348
  9  * @create 05/04 11:55
 10  * @description <p>
 11  * 模拟多线程售票
 12  * </p>
 13  */
 14 public class TicketThread extends Thread {
 15     // 假定票总是100张
 16     private static Integer num = 100;
 17     // 锁对象
 18     private static Object lockObject = new Object();
 19     // 同步锁
 20     private static final ReentrantLock lock = new ReentrantLock();
 21 
 22     @Override
 23     public void run() {
 24         while (num > 0) {
 25             sellTicketLock();
 26         }
 27         System.out.println("===售票结束===");
 28     }
 29 
 30     /**
 31      * 当使用字节码锁时,使用了synchronized (TicketThread.class) 的地方都会被锁住,即使在不同的方法内
 32      */
 33     public void method1() {
 34         synchronized (TicketThread.class) {
 35             System.out.println("=======method1=============");
 36         }
 37     }
 38 
 39     /**
 40      * 同步代码块实现
 41      * 探讨synchronized锁的是什么?
 42      * synchronized锁住的是括号里的对象,而不是代码。
 43      * <p>
 44      * 1. 当synchronized (TicketThread.class),程序执行正常,只用一份字节码,谁拥有字节码谁就有执行权
 45      * 2. synchronized (lockObject),程序执行正常,因为整个应用上下文只有一个lockObject对象,谁拥有lockObject对象,谁就有执行权
 46      * 3. synchronized (this),有重复售票的现象,this代表当前对象,拥有当前对象就拥有执行权,而在时间调用中我们创建了多个TicketThread对象,因此锁是无效的;
 47      * 4. synchronized (num),有重复售票现象,因为num是变动的,如果有2个线程都是num=99那么只能有其中一个线程有执行权,
 48      * 但是如果num数不一样可以同时执行,这里为后面讲出售不同的班次的车票时提高锁的效率埋下伏笔
 49      */
 50     private void sellTicket() {
 51         synchronized (TicketThread.class) {
 52             if (num > 0) {
 53                 // 获取线程名称
 54                 String threadName = this.getName();
 55                 System.out.println(threadName + "-正在出售第" + num + "张票");
 56                 // 模拟售票耗时20毫秒
 57                 try {
 58                     Thread.sleep(50);
 59                 } catch (InterruptedException e) {
 60                     e.printStackTrace();
 61                 }
 62                 --num;
 63             }
 64         }
 65     }
 66 
 67     /**
 68      * 同步方法实现锁
 69      * 使用同步方法是,不需要手动添加同步监听对象,
 70      * 如果是实例方法,那么默认的同步监听对象就是this,
 71      * 如果是静态方法,默认的同步监听对象是类的字节码对象;
 72      * 1.实例方法 private synchronized void sellTicketSyn(),默认的同步监听对象就是this,只能锁住同一个对象,在当前使用会出现重复售票;
 73      * 2.静态方法 private static synchronized void sellTicketSyn(),同步监听对象是类的字节码对象,相当于全局锁;
 74      */
 75     private static void sellTicketSyn() {
 76         if (num > 0) {
 77             // 获取线程名称
 78             // String threadName = this.getName();
 79             String threadName = Thread.currentThread().getName();
 80             System.out.println(threadName + "-正在出售第" + num + "张票");
 81             // 模拟售票耗时20毫秒
 82             try {
 83                 Thread.sleep(50);
 84             } catch (InterruptedException e) {
 85                 e.printStackTrace();
 86             }
 87             --num;
 88         }
 89     }
 90 
 91     /**
 92      * 同步锁-ReentrantLock
 93      * 1.加锁 lock.lock();
 94      * 2.必须手动释放锁 lock.unlock();
 95      */
 96     private void sellTicketLock() {
 97         // 对代码加锁
 98         lock.lock();
 99         try {
100             if (num > 0) {
101                 // 获取线程名称
102                 // String threadName = this.getName();
103                 String threadName = Thread.currentThread().getName();
104                 System.out.println(threadName + "-同步锁正在出售第" + num + "张票");
105                 // 模拟售票耗时20毫秒
106                 try {
107                     Thread.sleep(50);
108                 } catch (InterruptedException e) {
109                     e.printStackTrace();
110                 }
111                 --num;
112             }
113         } finally {
114             // 释放锁
115             lock.unlock();
116         }
117     }
118 }

3.线程等待与唤醒

需求:模拟蓄水池流量限制,为了简单更明白的理解线程等待与通知,这里假定模型为极端情况,
假设蓄水池的正常水位是1000吨,当有水时每次取走水1000吨,当没有水时每次加入1000吨,让这两个动作交互进行
分析:
1.编写水池对象,并提供当前水位字段,取水方法,加水方法
/**
* 加水方法
* 1.synchronized 对同一个水池采用同步判定
* 2.判断是否有水,有水则等待(this.wait()),否则加水
* 3.加完水后进行通知(this.notify())
*/
/**
* 取水方法
* 1.synchronized 对同一个水池采用同步判定
* 2.判断是否有水,无水则等待(this.wait()),否则取水
* 3.取完水后进行通知(this.notify())
*/
2.编写2个线程,一个线程取水,一个线程加水
3.启动测试,创建一个水池对象,将对象分别传入取水线程和加水线程,并启动线程

实现代码

测试对象

 1 package com.wfd360.thread;
 2 
 3 import com.wfd360.thread.demo06Wait.AddPollThread;
 4 import com.wfd360.thread.demo06Wait.Pool;
 5 import com.wfd360.thread.demo06Wait.ReducePollThread;
 6 
 7 /**
 8  * @author 姿势帝-博客园
 9  * @address https://www.cnblogs.com/newAndHui/
10  * @WeChat 851298348
11  * @create 05/05 3:18
12  * @description <p>
13  * 需求:模拟蓄水池流量限制,为了简单更明白的理解线程等待与通知,这里假定模型为极端情况,
14  * 假设蓄水池的正常水位是1000吨,当有水时每次取走水1000吨,当没有水时每次加入1000吨,让这两个动作交互进行
15  * 分析:
16  * 1.编写水池对象,并提供当前水位字段,取水方法,加水方法
17  * 2.编写2个线程,一个线程取水,一个线程加水
18  * 3.启动测试,创建一个水池对象,将对象分别传入取水线程和加水线程
19  *
20  * </p>
21  */
22 public class Test09Wait {
23     /**
24      * 测试加水取水交替进行,理解等待与通知
25      *
26      * @param args
27      */
28     public static void main(String[] args) {
29         // 创建水池对象
30         Pool pool = new Pool();
31         pool.setNum(1000);
32         // 创建加水线程,并启动
33         new AddPollThread(pool, "加水线程").start();
34         // 创建取水线程,并启动
35         new ReducePollThread(pool, "取水线程").start();
36     }
37 }
Test09Wait

水池对象

 1 package com.wfd360.thread.demo06Wait;
 2 
 3 /**
 4  * @author 姿势帝-博客园
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/05 3:28
 8  * @description <p>
 9  * 水池对象
10  * </p>
11  */
12 public class Pool {
13     // 水池编号
14     private Integer id;
15     // 水池当前水量
16     private Integer num;
17 
18     /**
19      * 加水方法
20      * 1.synchronized 对同一个水池采用同步判定
21      * 2.判断是否有水,有水则等待,否则加水
22      * 3.加完水后进行通知
23      */
24     public synchronized void addPoll() throws Exception {
25         String name = Thread.currentThread().getName();
26         // 当水位大于或等于1000时,线程进入等待状态
27         if (num >= 1000) {
28             System.out.println(name + "-进入等待加水状态,num=" + num);
29             this.wait();
30         }
31         System.out.println(name + "-准备加水,num=" + num);
32         this.num += 1000;
33         System.out.println(name + "-加水完成,num=" + num);
34         this.notify();
35     }
36     /**
37      * 取水方法
38      * 1.synchronized 对同一个水池采用同步判定
39      * 2.判断是否有水,无水则等待,否则取水
40      * 3.取完水后进行通知
41      */
42     public synchronized void reducePoll() throws Exception {
43         String name = Thread.currentThread().getName();
44         // 当水位大于或等于1000时,线程进入等待状态
45         if (num < 1000) {
46             System.out.println(name + "-进入等待--取水--状态,num=" + num);
47             this.wait();
48         }
49         System.out.println(name + "-准备--取水--,num=" + num);
50         this.num -= 1000;
51         System.out.println(name + "---取水--完成,num=" + num);
52         this.notify();
53     }
54 
55     public Integer getId() {
56         return id;
57     }
58 
59     public void setId(Integer id) {
60         this.id = id;
61     }
62 
63     public Integer getNum() {
64         return num;
65     }
66 
67     public void setNum(Integer num) {
68         this.num = num;
69     }
70 }
Pool

 取水线程

 1 package com.wfd360.thread.demo06Wait;
 2 
 3 /**
 4  * @author 姿势帝-博客园
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/05 3:41
 8  * @description
 9  */
10 public class ReducePollThread extends Thread {
11     private Pool pool;
12 
13     // 创建线程必须传入水池对象
14     public ReducePollThread(Pool pool,String name) {
15         this.pool = pool;
16         this.setName(name);
17     }
18 
19     @Override
20     public void run() {
21         // 循环取水100次
22         for (int i = 0; i < 100; i++) {
23             try {
24                 pool.reducePoll();
25             } catch (Exception e) {
26                 e.printStackTrace();
27             }
28         }
29     }
30 }
ReducePollThread

加水线程

 1 package com.wfd360.thread.demo06Wait;
 2 
 3 /**
 4  * @author 姿势帝-博客园
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/05 3:41
 8  * @description
 9  */
10 public class AddPollThread extends Thread {
11     private Pool pool;
12 
13     // 创建线程必须传入水池对象
14     public AddPollThread(Pool pool,String name) {
15         this.pool = pool;
16         // 设置线程名称
17         this.setName(name);
18     }
19 
20     @Override
21     public void run() {
22         // 循环加水100次
23         for (int i = 0; i < 100; i++) {
24             try {
25                 pool.addPoll();
26             } catch (Exception e) {
27                 e.printStackTrace();
28             }
29         }
30     }
31 }
AddPollThread

测试结果

 完美!

系统化的在线学习:点击进入学习

posted @ 2020-06-07 14:20  李东平|一线码农  阅读(373)  评论(0编辑  收藏  举报