系统化学习多线程(二)-线程同步-等待-通知
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 }
水池对象
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 }
取水线程
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 }
加水线程
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 }
测试结果
完美!
系统化的在线学习:点击进入学习