zoukankan      html  css  js  c++  java
  • 系统化学习多线程(二)-线程同步-等待-通知

    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

    测试结果

     完美!

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

  • 相关阅读:
    Effective C++ 读书笔记(3544):继承关系与面向对象设计
    《全景探秘游戏设计艺术》读后感之引子
    Effective C++ 读书笔记(1117):构造析构和赋值函数
    Effective C++ 读书笔记(2934):类与函数之实现
    Unity中使用PersistentDataPath加载文件
    打开本地【C】【D】【E】驱动器时候提示 X:\ 找不到应用程序
    C#进制转换
    在VS里编辑unity代码调用系统方法不显示中文注释或英文注释
    Spreadsheet说明
    C#中删除控件的事件的方法类.
  • 原文地址:https://www.cnblogs.com/newAndHui/p/12831089.html
Copyright © 2011-2022 走看看