• Java线程同步:synchronized、Lock锁


    线程同步概述

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

    线程安全问题出现的原因
    存在多线程并发,同时访问、修改共享资源

    如何保证线程安全?
    让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

    线程同步的核心思想
    加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。

    在这里插入图片描述


    synchronized 同步代码块

    使用方法

    把出现线程安全问题的核心代码给上锁。每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。

    语法格式

    synchronized(同步锁对象) {
    	操作共享资源的代码(核心代码)
    }
    
    • 1
    • 2
    • 3

    锁对象要求
    理论上锁对象只要对于当前同时执行的线程来说是同一个对象即可,但在实际操作中,应使用this关键字以实现只锁当前对象。 因为前者说的对所任意对象,会影响其他无关线程的执行。

    锁对象的规范要求
    1.使用共享资源作为锁对象;
    2.对于实例方法建议使用this作为锁对象;
    3.对于静态方法建议使用字节码(类名.class)对象作为锁对象;

    代码示例

    public class TestSafeDemo {
        public static void main(String[] args) {
            // 测试线程安全问题
            // 1、创建一个共享的账户对象。
            Account acc = new Account("ICBC-111" , 100000);
    
            // 2、创建2个线程对象,操作同一个账户对象
            new DrawThread(acc, "小明").start();
            new DrawThread(acc,"小红").start();
    
        }
    }
    
    public class DrawThread extends Thread{
        private Account acc;
        public DrawThread(Account acc, String name){
            super(name);
            this.acc = acc;
        }
    
        @Override
        public void run() {
            // 小明 小红  : acc
            acc.drawMoney(100000);
        }
    }
    
    public 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;
        }
    
    //    // 100个线程人
    //    public static void run(){
    //        synchronized (Account.class){
    //
    //        }
    //    }
    
    
        /**
          小明 小红
         */
        public void drawMoney(double money) {
            // 1、拿到是谁来取钱
            String name = Thread.currentThread().getName();
            // 同步代码块
            // 小明 小红
            // this == acc 共享账户
            synchronized (this) {
                // 2、判断余额是否足够
                if(this.money >= money){
                    // 钱够了
                    System.out.println(name+"来取钱,吐出:" + money);
                    // 更新余额
                    this.money -= money;
                    System.out.println(name+"取钱后,余额剩余:" + this.money);
                }else{
                    // 3、余额不足
                    System.out.println(name+"来取钱,余额不足!");
                }
            }
        }
    
    • 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
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    synchronized 同步方法

    使用方法

    把出现线程安全问题的核心方法给上锁,每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

    语法格式

    修饰符 synchronized 返回值类型 方法名称(形参列表) {
    	操作共享资源的代码
    }
    
    • 1
    • 2
    • 3

    同步方法底层原理
    1.同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码;
    2.如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象;
    3.如果方法是静态方法:同步方法默认用类名.class作为的锁对象;

    同步代码块、同步方法的区别
    同步代码块锁的范围更小,同步方法锁的范围更大。

    代码示例

    这里只列出了有变动的代码,具体运行测试和上面的代码结合起来即可。

    public 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;
        }
    
        /**
          小明 小红
           this == acc
         */
        public synchronized void drawMoney(double money) {
            // 1、拿到是谁来取钱
            String name = Thread.currentThread().getName();
            // 2、判断余额是否足够
            // 小明  小红
            if(this.money >= money){
                // 钱够了
                System.out.println(name+"来取钱,吐出:" + money);
                // 更新余额
                this.money -= money;
                System.out.println(name+"取钱后,余额剩余:" + this.money);
            }else{
                // 3、余额不足
                System.out.println(name+"来取钱,余额不足!");
            }
        }
    }
    
    • 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

    Lock锁

    使用方法

    1.为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便;
    2.Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作;
    3.Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象;

    构造器/方法说明
    public ReentrantLock​()获得Lock锁的实现类对象
    void lock()加锁
    void unlock()释放锁

    更多有关该类的使用方法建议阅读官方文档或看ReentrantLock类的源码。

    代码示例

    这里的锁在定义的时候应加上final关键字,以免后续代码对其误改动。

    public class Account {
        private String cardId;
        private double money; // 余额 关键信息
        // final修饰后:锁对象是唯一和不可替换的,非常专业
        private final Lock 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;
        }
    
        /**
          小明 小红
         */
        public void drawMoney(double money) {
            // 1、拿到是谁来取钱
            String name = Thread.currentThread().getName();
            // 2、判断余额是否足够
            // 小明  小红
            lock.lock(); // 上锁
            try {
                if(this.money >= money){
                    // 钱够了
                    System.out.println(name+"来取钱,吐出:" + money);
                    // 更新余额
                    this.money -= money;
                    System.out.println(name+"取钱后,余额剩余:" + this.money);
                }else{
                    // 3、余额不足
                    System.out.println(name+"来取钱,余额不足!");
                }
            } 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
  • 相关阅读:
    Windows系统安装PyTorch
    Docker 与 K8S学习笔记(二十二)—— 高效使用kubectl的小技巧
    力扣-3. 无重复字符的最长子串
    Playwright 组件测试入门
    物理层(2.1)
    924. 尽量减少恶意软件的传播 前缀和
    什么是私域流量,私域流量的媒介有哪些?
    项目干系人管理的10个关键原则
    Dockerfile编写实践篇
    148-153-Hadoop-调优-多目录-黑白名单
  • 原文地址:https://blog.csdn.net/weixin_52341477/article/details/125474023