• 【JavaSE】多线程篇(四)线程的同步机制、互斥锁、线程死锁与释放锁


    💁 个人主页:黄小黄的博客主页
    ❤️ 支持我:👍 点赞 🌷 收藏 🤘关注
    🎏 格言:All miracles start from sometime somewhere, make it right now.
    本文来自专栏:JavaSE从入门到精通
    在这里插入图片描述


    1 走进Synchronized

    1.1 线程同步机制

      在前面的多线程篇的学习中,我们可以尝试对售票活动进行模拟,将每个售票窗口看成一个进程。但是,前面由于没有学习过线程的同步,有可能会出现超卖的问题。比如只剩最后一张票,但是,两个窗口此时都在同时卖,就会由于数据更新不及时,导致多卖出票。

    🐱何为线程同步机制?

    1. 在多线程编程中,一些敏感数据不允许被多个线程同时访问, 此时就需要使用同步访问技术,保证数据在任何时刻,最多有一个线程被访问,以保证数据的完整性。
    2. 线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作。

    1.2 同步的具体方法–synchronized

    1️⃣ 同步代码块:

    synchronized(对象){//得到对象的锁,才能操作同步代码
    	//需要被同步的代码
    }
    
    • 1
    • 2
    • 3

    2️⃣ 同步方法:

    public synchronized void method(参数列表){
    	//需要被同步的代码
    }
    
    • 1
    • 2
    • 3

    1.3 使用线程同步解决售票问题

     在下面的案例中,我们模拟售票问题,通过synchronized解决超卖问题。

    package syn;
    
    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 售票问题
     */
    public class SellTicket {
        public static void main(String[] args) {
            SellTicketToCustomer sellTicketToCustomer = new SellTicketToCustomer();
            //模拟三个售票窗口
            new Thread(sellTicketToCustomer).start();
            new Thread(sellTicketToCustomer).start();
            new Thread(sellTicketToCustomer).start();
    
        }
    }
    
    class SellTicketToCustomer implements Runnable{
        /**
         * 票数
         */
        private int ticketNum = 100;
        /**
         * 控制run中的while,用于以通知的形式终止线程
         */
        private boolean loop = true;
    
        @Override
        public void run() {
            while (loop){
                sellTicket();
            }
        }
    
        /**
         * 售票的方法
         */
        public synchronized void sellTicket(){ //同步方法, 在统一时刻, 只能有一个线程来执行sellTicket方法
            if (ticketNum <= 0){
                System.out.println("售票结束...");
                loop = false;
                return;
            }
    
            try {
                Thread.sleep(50);
            }catch (InterruptedException e){
                e.getStackTrace();
            }
    
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票, "
                    + "剩余票数 = " + (--ticketNum));
        }
    }
    
    
    • 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

    🐰 说明:

    在上述例子中,sellTicket方法被设置为同步方法,在同一时刻,只允许一个线程进入,解决了超卖的情况。运行结果如下:
    在这里插入图片描述


    2 互斥锁

    2.1 基本介绍

    • Java语言引入了对象互斥锁的概念,来保证共享数据操作的完整性;
    • 每个对象都对应于一个可称为“互斥锁”的标记,这个标记 用来保证在任一时刻,只能有一个线程访问该对象;
    • 关键字synchronized用来与对象的互斥锁联系。当某个对象用synchronized修饰时,表示该对象在任一时刻只能由一个线程访问;
    • 同步具有局限性,会导致程序的执行效率降低;
    • 非静态同步方法的锁可以是this,也可以是其他对象(要求为同一对象);
    • 静态的同步方法的锁为当前类本身,类名.class。

    2.2 使用互斥锁解决售票问题

     售票问题我们既可以使用同步方法,也可以使用对象锁,给代码块上锁。在之前的案例中,我们演示的就是使用了同步方法:

    • public synchronized void sellTicket()就是一个同步方法,这时锁在 this 对象上。
      在这里插入图片描述

     这里,我们尝试 使用对象锁解决, 解决代码如下:
    在这里插入图片描述

      其实也不一定必须使用this,只要对象为同一个就行,比如下面的代码中,在该线程类中,初始化了一个对象,加锁代码块用的也是同一对象,因此,结果一致:

    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 售票问题
     */
    public class SellTicket {
        public static void main(String[] args) {
            SellTicketToCustomer sellTicketToCustomer = new SellTicketToCustomer();
            //模拟三个售票窗口
            new Thread(sellTicketToCustomer).start();
            new Thread(sellTicketToCustomer).start();
            new Thread(sellTicketToCustomer).start();
    
        }
    }
    
    class SellTicketToCustomer implements Runnable{
        /**
         * 票数
         */
        private int ticketNum = 100;
        /**
         * 控制run中的while,用于以通知的形式终止线程
         */
        private boolean loop = true;
        /**
         * 用于加锁
         */
        Object obj = new Object();
    
        @Override
        public void run() {
            while (loop){
                sellTicket();
            }
        }
    
        /**
         * 售票的方法
         */
        public void sellTicket(){
            synchronized (obj) {
                if (ticketNum <= 0) {
                    System.out.println("售票结束...");
                    loop = false;
                    return;
                }
    
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.getStackTrace();
                }
    
                System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票, "
                        + "剩余票数 = " + (--ticketNum));
            }
        }
    }
    
    
    • 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

    互斥锁小结一下:

    1. 同步方法如果没有static修饰,默认锁对象为this;
    2. 如果同步方法有static修饰,默认锁对象为 当前类.class;
    3. 注意多个线程的锁对象为同一个即可。

    3 线程死锁

    3.1 基本介绍

    多个线程都占用了对方的锁资源,但是不肯相让,就导致了死锁。 在实际编程开发中,一定要避免死锁的发生。

    🐦 举个例子:

    话说,在某一药店门口… …
    👮 你好,请您佩戴口罩!
    👦我没有口罩!
    👮 你好,没有口罩是不能入内的哦~
    👦我就是没有口罩才来买口罩啊!
    👮 没有佩戴口罩是不能进入的呢…
    👦… …
    👮 … …

     在上述例子中,我们可以简单的把口罩看作锁,把保安放人与客户通过分别看成一个线程。此时,保安和客户都占用口罩这一锁资源,互不相让,就导致了死锁的情况。

    3.2 案例演示

    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 模拟线程死锁
     */
    public class DeadLockTest {
        public static void main(String[] args) {
            DeadDemo deadDemo1 = new DeadDemo(true);
            DeadDemo deadDemo2 = new DeadDemo(false);
            Thread thread1 = new Thread(deadDemo1);
            Thread thread2 = new Thread(deadDemo2);
            thread1.start();
            thread2.start();
        }
    }
    
    /**
     * 线程
     */
    class DeadDemo implements Runnable{
        static Object o1 = new Object();
        static Object o2 = new Object();
        boolean flag;
    
        public DeadDemo(boolean flag){
            this.flag = flag;
        }
    
        @Override
        public void run() {
            if (flag){
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName() + "进入1");
                    synchronized (o2){
                        System.out.println(Thread.currentThread().getName() + "进入2");
                    }
                }
            }else {
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName() + "进入3");
                    synchronized (o1){
                        System.out.println(Thread.currentThread().getName() + "进入4");
                    }
                }
            }
        }
    }
    
    • 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

    在这里插入图片描述

    🐰 说明:
    在这里插入图片描述
     thread0与thread1两个线程运行时,thread0首先拿到o1锁,而后请求o2锁,这时,thread1已经拿到了o2锁,请求o1锁,由于各自请求的锁都被其他线程占用,就导致了死锁的情况, 运行结果就像卡住了一样,不再动弹。


    4 释放锁

    4.1 释放锁的情况

    1. 当前线程的同步方法、同步代码块执行结束;
    2. 当前线程在同步代码块、同步方法中遇到break、return;
    3. 当前线程在同步代码块、同步方法中出现了未处理的Error或者Exception,导致异常结束;
    4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

    4.2 不会释放锁的情况

    1. 线程执行同步代码块或者同步方法时,程序调用了Thread.sleep()、Thread.yield()方法,暂停了当前线程的执行,不会释放锁;
    2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将线程挂起,该线程不会释放锁。

    写在最后

    🌟以上便是本文的全部内容啦,后续内容将会持续免费更新,如果文章对你有所帮助,麻烦动动小手点个赞 + 关注,非常感谢 ❤️ ❤️ ❤️ !
    如果有问题,欢迎私信或者评论区!
    在这里插入图片描述
    共勉:“你间歇性的努力和蒙混过日子,都是对之前努力的清零。”
    以上图片出自一位重要的朋友,嘿嘿,祝愿一起学习的码友们:“前程似锦,万事胜意!”
    在这里插入图片描述

  • 相关阅读:
    webpack使用入门贴
    手游模拟器长时间运行后,游戏掉帧且不恢复
    MySQL阻塞与死锁
    什么是云存储,从对象存储说起?
    java计算机毕业设计开放式教学评价管理系统源码+mysql数据库+系统+lw文档+部署
    SpringBoot(一、快速入门)
    奥特曼与钢铁侠【InsCode Stable Diffusion美图活动一期】
    Redis篇---第七篇
    J2EE基础:自定义mvc01
    Python3 第十七课 -- 编程第一步
  • 原文地址:https://blog.csdn.net/m0_60353039/article/details/126695964