• 【多线程】线程安全与线程同步


    线程安全与线程同步

    1.什么是线程安全问题

    多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题

    取钱的线程安全问题场景:

    两个人他们有一个共同的账户,余额是10万元,如果两个人同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题?
    (1)线程安全问题出现的原因:

    • 存在多个线程在同时执行
    • 同时访问一个共享资源
    • 存在修改该共享资源
      在这里插入图片描述
    2.线程同步

    线程同步就是解决线程安全问题的方案

    (1)线程同步的思想:让多个线程实现先后依次访问共享资源,这样就解决了安全问题

    (2)线程同步的常见方案

    • 加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来

    (3)线程同步方式

    • 方式一:同步代码块

      ①作用:把访问共享资源的核心代码给上锁,以此保证线程的安全

      ②原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行

      ③同步锁的注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug

      ④锁对象不能随便选择一个唯一的对象,会影响其他无关线程的执行,例如String字符串

      ⑤锁对象的使用规范

      • 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象
      • 对于静态方法建议使用字节码(类名.class)对象作为锁对象
    public class Demo {
        public static void main(String[] args) {
            //3、创建账户对象(共享数据)
            Account acc = new Account("9527", 100000);
            //4、两个线程同时取款(多个线程操作共享数据,出现线程安全问题)
            new MyThread(acc, "小明").start();
            new MyThread(acc, "小红").start();
        }
    }
    //2、线程类,封装取款的代码
    class MyThread extends Thread {
        private Account acc;
    
        public MyThread(Account acc, String name) {
            super(name);
            this.acc = acc;
        }
    
        @Override
        public void run() {
            //取钱时,要传递取款金额
            acc.drawMoney(100000);
        }
    }
    
    //1、账户类,包含取款功能
    class Account {
        //卡号
        private String cardID;
        //余额
        private double money;
    
        public Account() {
        }
    
        public Account(String cardID, double money) {
            this.cardID = cardID;
            this.money = money;
        }
    
        public String getCardID() {
            return cardID;
        }
    
        public void setCardID(String cardID) {
            this.cardID = cardID;
        }
    
        public double getMoney() {
            return money;
        }
    
        public void setMoney(double money) {
            this.money = money;
        }
        //取款功能,参数money为取款金额
        public void drawMoney(double money) {
            //获取当前线程对象的名称
            String name = Thread.currentThread().getName();
            synchronized (this) {
                if (this.money >= money) {
                    System.out.println(name + "取钱:" + money + "成功");
                    this.money -= money;
                    System.out.println(name + "取钱后余额为:" + this.money);
                } else {
                    System.out.println(name + "取钱失败,余额不足");
                }
            }
        }
        //对于静态方法建议使用字节码(类名.class)对象作为锁对象
        public static void test(){
            synchronized (Account.class){
    
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    在这里插入图片描述

    • 方式二:同步方法

      ①作用:把访问共享资源的核心方法上锁,以此保证线程安全

      ②原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行

      ③同步方法的底层原理

      • 同步方法其实底层也是由隐式锁对象的,只是锁的范围是整个方法代码
      • 如果方法是实例方法:同步方法默认this作为锁的对象
      • 如果方法是静态方法:同步方法默认用类名.class作为锁对象
    //线程类和测试类同上
    //1、账户类,包含取款功能
    class Account {
        //卡号
        private String cardID;
        //余额
        private double money;
    
        public Account() {
        }
    
        public Account(String cardID, double money) {
            this.cardID = cardID;
            this.money = money;
        }
    
        public String getCardID() {
            return cardID;
        }
    
        public void setCardID(String cardID) {
            this.cardID = cardID;
        }
    
        public double getMoney() {
            return money;
        }
    
        public void setMoney(double money) {
            this.money = money;
        }
    
        //取款功能,参数money为取款金额
        public synchronized void drawMoney(double money) {
            String name = Thread.currentThread().getName();
            if (this.money >= money) {
                System.out.println(name + "取钱:" + money + "成功");
                this.money -= money;
                System.out.println(name + "取钱后余额为:" + this.money);
            } else {
                System.out.println(name + "取钱失败,余额不足");
            }
        }
        //静态方法
        public synchronized static void test(){
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    ④同步代码块和同步方法的区别

    同步代码块:锁对象可以指定,锁的范围可以指定,性能高且灵活

    同步方法:锁对象不能指定,锁的是方法体,阅读性更高

    • 方式三:Lock锁

      ①Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大

      ②Lock是接口,不能直接被实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象

      public ReentrantLock():获得Lock锁的实现类对象

      ③Lock的常用方法

      • void lock():获得锁
      • void unlock():释放锁

      ④Lock锁使用规范

      • 锁对象创建在成员位置,使用final修饰
      • 释放锁的代码写在finally块中
      public class Demo {
          public static void main(String[] args) {
              //3、创建账户对象(共享数据)
              Account acc = new Account("9527", 100000);
              //4、两个线程同时取款(多个线程操作共享数据,出现线程安全问题)
              new MyThread(acc, "小明").start();
              new MyThread(acc, "小红").start();
          }
      }
      //2、线程类,封装取款的代码
      class MyThread extends Thread {
          private Account acc;
      
          public MyThread(Account acc, String name) {
              super(name);
              this.acc = acc;
          }
      
          @Override
          public void run() {
              //取钱时,要传递取款金额
              acc.drawMoney(100000);
          }
      }
      //1、账户类,包含取款功能
      class Account {
          //卡号
          private String cardID;
          //余额
          private double money;
          //规范1、锁对象创建在成员位置,使用final修饰
          private final ReentrantLock lock = new ReentrantLock();
          public Account() {
          }
      
          public Account(String cardID, double money) {
              this.cardID = cardID;
              this.money = money;
          }
      
          public String getCardID() {
              return cardID;
          }
      
          public void setCardID(String cardID) {
              this.cardID = cardID;
          }
      
          public double getMoney() {
              return money;
          }
      
          public void setMoney(double money) {
              this.money = money;
          }
      
          //取款功能,参数money为取款金额
          public void drawMoney(double money) {
              String name = Thread.currentThread().getName();
              try {
                  //上锁
                  lock.lock();
                  if (this.money >= money) {
                      System.out.println(name + "取钱:" + money + "成功");
                      this.money -= money;
                      System.out.println(name + "取钱后余额为:" + this.money);
                  } else {
                      System.out.println(name + "取钱失败,余额不足");
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  //释放锁
                  lock.unlock();
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
  • 相关阅读:
    k8s以及prometheus
    linux安装UnZip
    如何自己录制教学视频?零基础也能上手
    多目标水循环优化算法附Matlab代码
    vue 的实例生命周期
    十三水中各种牌型判断LUA版
    1.虚拟机无法连接网络,且无法ping通的问题解决
    java计算机毕业设计江西婺源旅游文化推广系统源码+mysql数据库+系统+lw文档+部署
    如何在 macOS 上安装 Docker Desktop
    热销产品缺货,滞销产品积压?WMS系统如何打造智能仓储
  • 原文地址:https://blog.csdn.net/m0_65462447/article/details/132740383