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

1.大纲

系统化学习多线程(二)-线程同步-等待-通知_第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 

11 * 模拟多线程售票 12 *

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 *

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 

13 * 需求:模拟蓄水池流量限制,为了简单更明白的理解线程等待与通知,这里假定模型为极端情况, 14 * 假设蓄水池的正常水位是1000吨,当有水时每次取走水1000吨,当没有水时每次加入1000吨,让这两个动作交互进行 15 * 分析: 16 * 1.编写水池对象,并提供当前水位字段,取水方法,加水方法 17 * 2.编写2个线程,一个线程取水,一个线程加水 18 * 3.启动测试,创建一个水池对象,将对象分别传入取水线程和加水线程 19 * 20 *

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 

9 * 水池对象 10 *

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

测试结果

系统化学习多线程(二)-线程同步-等待-通知_第2张图片

 完美!

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

你可能感兴趣的:(系统化学习多线程(二)-线程同步-等待-通知)