• 八锁现象——synchronized关键字实战详解


    八锁现象

    羡慕案例中使用sleep方法制造延迟来测试。

    学习目标: 什么是锁?锁是什么?如何判断锁的是谁

    哪八种锁?

    1、一个对象,俩个同步方法

    2、一个对象,俩个同步方法,一个方法延迟

    3、两个对象,两个同步方法

    4、一个对象,一个同步,一个普通方法

    5、一个对象,俩个静态同步方法

    6、两个对象,俩个静态同步方法

    7、一个对象,一个静态的同步方法,一个同步方法

    8、两个对象,一个静态的同步方法,一个同步方法

    八锁第一锁和第二锁

    第一锁

    现象演示

    两个线程调用Phone执行Phone下的发短信和打电话方法

    1、标准情况下,两个线程先执行发短信,再打电话。

    结果:发短信先执行

    import java.util.concurrent.TimeUnit;
    
    /**
     * 八锁,就是关于锁的八个问题
     * 1、标准情况下,两个线程先执行发短信,再打电话
     */
    public class Test1 {
        public static void main(String[] args) {
            Phone phone = new Phone();
    
            // 线程A发短信
            new Thread(()->{phone.sendSms();},"A").start();
            // 休息一秒
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 线程B打电话
            new Thread(()->{phone.call();},"B").start();
        }
    }
    class Phone{
        // 发短信
        public synchronized void sendSms(){
            System.out.println("发短信");
        }
        // 打电话
        public synchronized void call(){
            System.out.println("打电话");
        }
    }
    
    • 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

    第二锁

    现象演示

    2、sendSms方法延时4秒,仍然是先执行发短信,再打电话。所以我们排除执行顺序是按照线程创建的先后顺序

    结果:发短信先执行

    import java.util.concurrent.TimeUnit;
    
    /**
     * 八锁,就是关于锁的八个问题
     * 2、sendSms方法延时4秒,仍然是先执行发短信,再打电话
     */
    public class Test1 {
        public static void main(String[] args) {
            Phone phone = new Phone();
    
            // 线程A发短信
            new Thread(()->{phone.sendSms();},"A").start();
            // 休息一秒
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 线程B打电话
            new Thread(()->{phone.call();},"B").start();
        }
    }
    class Phone{
        // 发短信
        public synchronized void sendSms(){
            // 休息四秒
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        // 打电话
        public synchronized void call(){
            System.out.println("打电话");
        }
    }
    
    • 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

    第一、二锁解释

    被synchronized修饰的方法,锁的对象是方法的调用者,此方法中均是对象Phone来调用的,即两个方法调用的对象是同一个、使用的同一把锁,谁先拿到就谁先执行。
    
    • 1

    八锁第三锁

    现象:phone、phone1两个不同对象在两个线程A、B里分别调用发短信、打电话,其中发短信方法有4秒休眠。

    结果:打电话先执行

    import java.util.concurrent.TimeUnit;
    
    /**
    也可以使用Phone phone = new Phone();Phone phone2 = new Phone();phone在线程A中调用发短信,phone2在线程B中调用打电话,这里锁的是两个不同的调用者,所以他们是两把锁,所以互不影响,因为发短信中有4秒的线程休眠,故而一定是打电话被先调用
     */
    public class Test2 {
        public static void main(String[] args) {
            Phone2 phone = new Phone2();
            Phone2 phone1 = new Phone2();
            /**
             * 被synchronized 修饰的方式和普通方法 先执行sendSms() 还是 hello()
             * 答案: hello()
             *  解释:新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!
             */
            // 线程A调用phone发短信
            new Thread(()->{phone.sendSms();},"A").start();
            // 休息一秒
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 线程B使用phone1调用打电话
            new Thread(()->{phone1.call();},"B").start();
        }
    }
    
    class Phone2{
        // 发短信
        public synchronized void sendSms(){
            // 休息四秒
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        // 打电话
        public synchronized void call(){
            System.out.println("打电话");
        }
    }
    
    • 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

    第三锁解释

    使用Phone phone = new Phone();Phone phone2 = new Phone();phone在线程A中调用发短信,phone2在线程B中调用打电话,这里锁的是两个不同的调用者,所以他们是两把锁,所以互不影响,因为发短信中有4秒的线程休眠,故而一定是打电话被先调用
    
    • 1

    八锁第四锁

    现象:phone对象在线程A中调用被synchronized修饰的发短信方法 和 在线程B中调用未被synchronized修饰的普通方法hello

    结果:hello先执行

    import java.util.concurrent.TimeUnit;
    
            /**
             * 被synchronized 修饰的方法和普通方法 先执行sendSms() 还是 hello()
             * 答案: hello()
             *  解释:新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!
             */
    public class Test2 {
        public static void main(String[] args) {
            Phone2 phone = new Phone2();
            // 线程A发短信
            new Thread(()->{phone.sendSms();},"A").start();
            // 休息一秒
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 线程B调用未被synchronized修饰的方法hello()
            new Thread(()->{phone.hello();},"B").start();
        }
    }
    
    class Phone2{
        // 发短信
        public synchronized void sendSms(){
            // 休息四秒
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        // 打电话
        public synchronized void call(){
            System.out.println("打电话");
        }
    
        // say hello 没有被锁 也就是说不是同步方法、不受锁的影响,线程会直接把它来执行
        public void hello(){
            System.out.println("hello");
        }
    }
    
    • 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

    第四锁解释

    被synchronized 修饰的方法和普通方法 先执行sendSms() 还是 hello()?
    答案是hello()解释:新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!
    
    • 1
    • 2

    八锁第五锁

    现象:Phone3在线程A 和 线程B中分别调用Phone3的被synchronized和static修饰的静态同步方法 发短信 和 打电话。

    结果:发信息先调用

    import java.util.concurrent.TimeUnit;
    
    /**
     * 将两个同步方法设置为静态
     */
    public class Test3 {
        public static void main(String[] args) {
            // 线程A发短信
            new Thread(()->{Phone3.sendSms();},"A").start();
    
            // 休息一秒
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 线程B打电话
            new Thread(()->{Phone3.call();},"B").start();
        }
    }
    
    class Phone3{
    
        /**
         * 在此处我们将 两个同步方法均设为静态方法,此两个方法在类加载是被加载
         * 所以,在此2个同步方法加载时,它们被全局唯一的Phone3.class对象加载,因为此Phone3.class全局唯一,故而他们被同一个对象加载
         * 所以此时仍然是发短信先输出
         */
    
        // 发短信
        public static synchronized void sendSms(){
            // 休息四秒
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        // 打电话
        public static synchronized void call(){
            System.out.println("打电话");
        }
    }
    
    • 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

    第五锁解释

    只要方法被 static 修饰,锁的对象就是 Class模板对象,这个则全局唯一!所以说这里是同一个锁,并不是因为synchronized  这里程序会从上往下依次执行
    
    • 1

    八锁第六锁

    现象:phone、phone1两个对象来分别调用两个静态同步方法,哪个先执行?

    结论:发短信先执行

    import java.util.concurrent.TimeUnit;
    
    /**
     */
    public class Test3 {
        public static void main(String[] args) {
            // 因为sendSms和call方法均是静态方法,故而Phone3.sendSms()和new Phone3().sendSms()没有区别
            Phone3 phone = new Phone3();
            Phone3 phone1 = new Phone3();
            // 线程A发短信
            new Thread(()->{phone.sendSms();},"A").start();
    
            // 休息一秒
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 线程B打电话
            new Thread(()->{phone1.call();},"B").start();
        }
    }
    
    class Phone3{
    
        // 发短信
        public static synchronized void sendSms(){
            // 休息四秒
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        // 打电话
        public static synchronized void call(){
            System.out.println("打电话");
        }
    }
    
    • 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

    第六锁解释

    /**
     * 同被static+synchronized 修饰的两个方法,是先发短信还是先打电话()?
     *  答案:发短信
     *  解释:只要方法被 static 修饰,锁的对象就是 Class模板对象,这个则全局唯一,class模板只有一个,即使是phone和phone1不是同以次实例化的对象,但是无论 phone.静态方法() 还是 phone1.静态方法() 它们均相当于Phone.静态方法()
     *  所以说这里是同一个锁,并不是因为synchronized导致。
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    八锁第七锁

    现象:被synchronized修饰的普通方法和被synchronized静态方法是先发短信还是先打电话?

    结论:先打电话

    import java.util.concurrent.TimeUnit;
    
    /**
     */
    public class Test3 {
        public static void main(String[] args) {
            // 因为sendSms和call方法均是静态方法,故而Phone3.sendSms()和new Phone3().sendSms()没有区别
            Phone3 phone = new Phone3();
    
            // 线程A发短信
            new Thread(()->{phone.sendSms();},"A").start();
    
            // 休息一秒
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 线程B打电话
            new Thread(()->{phone.call();},"B").start();
        }
    }
    
    class Phone3{
        // 发短信
        public static synchronized void sendSms(){
            // 休息四秒
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        // 打电话
        public synchronized void call(){
            System.out.println("打电话");
        }
    }
    
    • 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

    第七锁解释

    /**
     * 被synchronized修饰的普通方法和被synchronized静态方法是先发短信还是先打电话?
     * 答案:打电话
     * 解释:只要被static修饰锁的是class模板, 而synchronized 锁的是调用的对象
     * 这里是两个锁互不影响,按时间先后执行
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    八锁第八锁

    现象:phone、phone1两个对象来分别调用被static+synchronized修饰的静态同步方法发短信 和 被synchronized修饰的普通方法打电话,哪个先执行?

    结论:随机执行

    import java.util.concurrent.TimeUnit;
    
    /**
     */
    public class Test3 {
        public static void main(String[] args) {
            // 因为sendSms和call方法均是静态方法,故而Phone3.sendSms()和new Phone3().sendSms()没有区别
            Phone3 phone = new Phone3();
            Phone3 phone1 = new Phone3();
    
            // 线程A发短信
            new Thread(()->{phone.sendSms();},"A").start();
    
            // 休息一秒
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 线程B打电话
            new Thread(()->{phone1.call();},"B").start();
        }
    }
    
    class Phone3{
        // 发短信
        public static synchronized void sendSms(){
            // 休息四秒
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        // 打电话
        public synchronized void call(){
            System.out.println("打电话");
        }
    }
    
    • 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

    第八锁解释

    phone、phone1两个对象来分别调用被static+synchronized修饰的静态同步方法发短信 和 被synchronized修饰的普通方法打电话,先执行发短信还是打电话?
    答案:打电话
    解释: 只要被static 修饰的锁的就是整个class模板
    这里一个锁的是class模板 一个锁的是调用者 
    所以锁的是两个对象,互不影响,按CPU调度顺序执行
    
    • 1
    • 2
    • 3
    • 4
    • 5
    八锁总结
    synchronized(Demo.class){
    }
    和
    synchronized(this){
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1、new this 调用的是这个对象,是一个具体的对象!
    2、static class 唯一的一个模板,即Class对象!

    synchronized 锁的对象是方法的调用者

    • 普通同步方法的调用者是类的实例(实例化对象)
    • 静态同步方法的调用者是类的对象(class对象)

    在我们编写多线程程序得时候,只需要搞明白这个到底锁的是什么就不会出错了!


  • 相关阅读:
    含文档+PPT+源码等]基于SpringBoot的阳光线上交友系统包运行成功]计算机毕业设计Java毕设源码
    PLSQL工具 数据库连接名的设置
    GPT-4成Nature审稿人,超 50% 结果和人类评审一致
    【小程序】HbuilderX搭建开发环境
    【LeetCode每日一题】——950.按递增顺序显示卡牌
    图计算中的世界观?推荐一本值得反复阅读的书籍
    清览题库--C语言程序设计第五版编程题解析(2)
    【自然语言处理(NLP)】基于注意力机制的中-英机器翻译
    操作系统知识点总结——第五章输入/输出管理
    英语小作文写作模板及步骤(1)
  • 原文地址:https://blog.csdn.net/qq_44778023/article/details/130839825