• 【Java多线程】线程同步机制(含同步方法)及不安全案例讲解


    ➤ Java多线程编程【一文全解】

    线程同步机制

    • 多个线程操作同一个资源 ,例如:
      • 上万人同时抢100张票;
      • 两个银行同时取钱

    现实生活中,会遇到“同一个资源,多个人都想使用”的问题,例如:食堂排队打饭,每个人都想吃饭,最简单的解决办法就是,排队,一个一个来。

            处理多线程时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候需要线程同步;线程同步其实就是一种 等待机制,多个需要同时访问此对象的线程进入到这个 对象的等待池 形成队列,等待前面线程使用完毕,下一个线程再使用。

    这个时候需要两种东西: 队列

    > 线程同步

            由于同一个进程的多个线程共享同一个存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题;

    ※ 不安全案例

    01 不安全的买票

    线程不同步,可能会出现拿到 -1张票的情况

    //不安全的买票
    //线程不安全,会出现负数
    public class UnsafeBuyTicket {
        public static void main(String[] args){
            BuyTicket station = new BuyTicket();
    
            new Thread(station,"你").start();
            new Thread(station,"我").start();
            new Thread(station,"他").start();
        }
    }
    class BuyTicket implements Runnable{
        //票
        private int ticketNums = 10;
        boolean flag = true;//外部停止方式
        @Override
        public void run(){
            while(flag){
                try {
                    buy();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        private void buy() throws InterruptedException {
            //判断是否有票
            if(ticketNums <=0){
                flag = false;
                return;
            }
            //模拟延时
            Thread.sleep(100);
            //买票
            System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
        }
    }
    
    • 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

    02 不安全的银行

    多个线程同时对银行发起取钱,会导致出现取多了的情况

    //不安全的取钱
    //两个人去银行取钱,账户
    public class UnsafeBank {
        public static void main(String[] args) {
            //账户
            Account account = new Account(100,"基金");
    
            Drawing you = new Drawing(account,50,"你");
            Drawing me = new Drawing(account,100,"我");
    
            you.start();
            me.start();
        }
    }
    //账户
    class Account {
        int money;//余额
        String name;//卡名
        public Account(int money,String name){
            this.money = money;
            this.name = name;
        }
    }
    //银行:模拟取款
    class Drawing extends Thread{
        Account account;//账户
        //取了多少钱
        int drawingMoney;
        //现在手里有多少钱
        int nowMoney;
        public Drawing(Account account,int drawingMoney,String name){
            super(name);
            this.account = account;
            this.drawingMoney = drawingMoney;
    
        }
        //取钱
        @Override
        public void run(){
            //判断有没有钱
            if(account.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //卡内余额 = 余额 - 取走的钱
            account.money = account.money - drawingMoney;
            //手里的钱
            nowMoney = nowMoney+ drawingMoney;
            System.out.println(account.name+"余额为"+account.money);
            //Thread.currentThread().getName() = this.getName;
            System.out.println(this.getName()+"手里的钱"+nowMoney);
        }
    } /*        基金余额为-50
                你手里的钱50
                基金余额为-50
                我手里的钱100
            
                进程已结束,退出代码0
    */
    
    • 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

    03 线程不安全的集合

    //线程不安全的集合
    public class UnsafeList {
        public static void main(String[] args) throws InterruptedException {
            List<String> list = new ArrayList<String>();
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{
                    list.add(Thread.currentThread().getName());
                }).start();
            }
            System.out.println(list.size());
        }
    }  /*   9997
    
            进程已结束,退出代码0
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    > 同步方法及方法块

            由于可以通过 private 关键字可以保证数据对象只能被方法访问,所以只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:

    • synchronized 方法 和 synchronized 块

    同步方法:

    public synchronized void method( int args){}
    
    • 1

            synchronized 方法控制对 “对象” 的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

    • 缺陷:若将一个大的方法声明为 synchronized 将会影响效率

    • 弊端:方法里面需要修改的内容才需要锁,锁的太多,浪费资源,这个时候就需要 同步块 来解决

    同步块:

    • 同步块: synchronized ( Obj ) { }
    • Obj 称之为同步监视器
      • Obj可以为任何对象,推荐使用共享资源作为同步监视器;
      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this 这个对象本身,或者是 class;
    • 同步监视器的执行过程:
    1. 第一个线程访问,锁定同步监视器,执行其中代码;
    2. 第二个线程访问,发现同步监视器被锁定,无法访问;
    3. 第一个线程访问完毕,解锁同步监视器;
    4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问;

    解决之前不安全的买票:(同步方法)

    //安全的买票
    public class UnsafeBuyTicket {
        public static void main(String[] args){
            BuyTicket station = new BuyTicket();
    
            new Thread(station,"你").start();
            new Thread(station,"我").start();
            new Thread(station,"他").start();
        }
    }
    
    class BuyTicket implements Runnable{
        //票
        private int ticketNums = 10;
        boolean flag = true;//外部停止方式
    
        @Override
        public void run(){
            while(flag){
                try {
                    buy();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        //synchronized 定义为同步方法,锁的是 this
        private synchronized void buy() throws InterruptedException {
            //判断是否有票
            if(ticketNums <=0){
                flag = false;
                return;
            }
            //模拟延时
            Thread.sleep(100);
            //买票
            System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
        }
    }
    
    • 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

    解决不安全的银行:(同步块)

    //安全的取钱
    //两个人去银行取钱,账户
    public class UnsafeBank {
        public static void main(String[] args) {
            //账户
            Account account = new Account(100,"基金");
    
            Drawing you = new Drawing(account,50,"你");
            Drawing me = new Drawing(account,100,"我");
    
            you.start();
            me.start();
    
        }
    
    }
    
    //账户
    class Account {
        int money;//余额
        String name;//卡名
    
        public Account(int money,String name){
            this.money = money;
            this.name = name;
        }
    }
    
    //银行:模拟取款
    class Drawing extends Thread{
        Account account;//账户
        //取了多少钱
        int drawingMoney;
        //现在手里有多少钱
        int nowMoney;
    
        public Drawing(Account account,int drawingMoney,String name){
            super(name);
            this.account = account;
            this.drawingMoney = drawingMoney;
    
        }
    
        //取钱
        @Override
        public void run(){
        	//全部放到同步块中 监视account
            synchronized(account) {
                //判断有没有钱
                if(account.money-drawingMoney<0){
                    System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                    return;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //卡内余额 = 余额 - 取走的钱
                account.money = account.money - drawingMoney;
                //手里的钱
                nowMoney = nowMoney+ drawingMoney;
    
                System.out.println(account.name+"余额为"+account.money);
                //Thread.currentThread().getName() = this.getName;
                System.out.println(this.getName()+"手里的钱"+nowMoney);
            }
        }           
    }/*     基金余额为50
            你手里的钱50
            我钱不够,取不了
    
            进程已结束,退出代码0
    */
    
    • 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

    解决不安全的集合:(同步块)

    //线程安全的集合
    public class UnsafeList {
        public static void main(String[] args) throws InterruptedException {
            List<String> list = new ArrayList<String>();
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{
                    synchronized (list){
                        list.add(Thread.currentThread().getName());
                    }
                }).start();
            }
            Thread.sleep(3000);
            System.out.println(list.size());
        }
    }  /*   10000
    
            进程已结束,退出代码0
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ※ 安全类型的集合 CopyOnWriteArrayList

    import java.util.concurrent.CopyOnWriteArrayList;
    //测试JUC安全类型的集合 CopyOnWriteArrayList
    public class UnsafeList {
        public static void main(String[] args) throws InterruptedException {
        
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
            
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{
                        list.add(Thread.currentThread().getName());
                }).start();
            }
            Thread.sleep(3000);
            System.out.println(list.size());
        }
    }  /*   10000
    
            进程已结束,退出代码0
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    C# WPF 桌面应用程序使用 SQlite 数据库
    基于Java+SpringBoot+Thymeleaf+Mysql新冠疫苗预约系统设计与实现
    Centos---命令详解 vi 系统服务 网络
    深入了解 AndroidX ConstraintLayout 中的 Barrier
    React 全栈体系(十二)
    实验四、零比特插入《计算机网络》
    zookeeper基础学习之六: zookeeper java客户端curator
    Spring Cloud Alibaba(四)
    Knife4j Spring Boot:在线API文档
    [Linux系统编程]_网络编程(五)
  • 原文地址:https://blog.csdn.net/Lov1_BYS/article/details/128062383