【JUC中的 “8锁现象” 】 —— 每天一点小知识

简介: 【JUC中的 “8锁现象” 】 —— 每天一点小知识

JUC中的8锁现象

   💧在Java并发编程中,锁是一种关键的同步机制,用于控制多个线程对共享资源的访问。然而,在某些情况下,使用锁可能会引发性能问题,其中一个典型例子就是JUC中的8锁现象,也被称为锁粗化。


🌊什么是8锁现象?

   💧8锁现象指的是在多线程环境下,当多个线程同时对同一个对象的不同锁进行操作时,可能导致性能下降的现象。每个对象都有一个与之关联的监视器锁,也称为内置锁或互斥锁。当一个线程试图进入一个同步代码块时,它需要先获得该对象的监视器锁。如果锁已经被另一个线程获得,则线程将被阻塞,直到锁被释放。


   💧在并发编程中,当多个线程竞争同一个对象的不同锁时,会产生线程切换和竞争的开销,从而导致性能下降。这是因为每个对象都有自己的锁,并且每个锁都有自己的计数器和等待队列。当多个线程同时竞争同一个对象上的不同锁时,会频繁地发生线程切换,造成性能瓶颈。


🌊为什么会产生8锁现象?

   💧8锁现象的产生与Java虚拟机(JVM)对锁的优化机制有关。在某些情况下,JVM会尝试对连续的同步块进行优化,将多个锁的获取和释放合并为一个更大的同步块,从而减少线程之间的竞争和切换。这个过程称为锁粗化。


   💧然而,当多个线程对同一个对象的不同锁进行操作时,JVM无法进行锁粗化优化。每个锁都需要独立获取和释放,导致频繁的线程切换和竞争,从而降低并发性能。


🌊如何避免8锁现象?

   💧为了避免8锁现象带来的性能问题,可以尝试以下优化策略:


合并锁: 如果多个锁操作的是同一个对象,可以考虑将它们合并成一个锁。通过合并相同对象的多个锁,减少锁的竞争,从而提高程序的并发性能。这需要仔细设计代码结构,确保多个锁的操作在合并后仍然保持独立性和正确性。


细粒度锁: 通过将对象分解成更小的粒度,可以减小锁的粒度。如果可能,可以将一个大的共享对象拆分为多个小的独立对象,每个对象使用自己的锁。这样可以使得多个线程可以同时访问不同的锁,减少竞争和线程切换的开销。细粒度锁同样需要对代码进行仔细的分析和设计,以确保线程安全和正确性。


使用并发集合类: Java提供了一些高效的并发集合类,如ConcurrentHashMap和ConcurrentLinkedQueue。这些并发集合类内部实现了细粒度的并发控制机制,可以减少对锁的依赖,提高并发性能。通过使用这些并发集合类,可以避免手动管理锁带来的复杂性,同时保证线程安全和高效的并发访问。


使用并发框架: JUC(Java Util Concurrent)中提供了一些高级的并发编程框架,如线程池和Future模式。通过使用这些框架,可以更好地管理线程和任务,提高并发性能和可扩展性。线程池可以重用线程,减少线程创建和销毁的开销;Future模式可以异步执行任务并获取结果,提高程序的并发度。


需要注意的是,优化锁的使用需要根据具体的业务场景和代码结构进行分析和调整。过度优化可能会引入新的问题或复杂性。在进行优化之前,建议进行性能测试和基准测试,以确保优化策略的有效性和稳定性。

🌊狂神谈 “8锁现象”

8锁,就是关于锁的8个问题。如何判断锁的是谁!永远的知道什么锁,锁到底锁的是谁!

下面通过 4 个案例来直观的感受一下 “8锁现象”

案例一(先打印“发短信”)

  • 标准情况下,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话。
  • sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话。
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        //锁的存在
        new Thread(()->{
            phone.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        },"B").start();
    }
}
class Phone{
    // synchronized 锁的对象是方法的调用者!、
    // 两个方法用的是同一个锁,谁先拿到谁执行!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

   💧案例一总结:synchronized 锁的对象是方法的调用者,两个方法用的是同一个锁,谁先拿到谁执行。

案例二(先打印“打电话”)

  • 增加了一个普通方法后!先执行发短信还是Hello? 普通方法。
  • 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
public class Test2  {
    public static void main(String[] args) {
        // 两个对象,两个调用者,两把锁!
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
class Phone2{
    // synchronized 锁的对象是方法的调用者!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
    // 这里没有锁!不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}

 💧案例二总结:synchronized 锁的对象是方法的调用者,hello() 没有锁!不是同步方法,所以不受锁的影响。


案例三(先打印“发短信”)

增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?

两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?

public class Test3  {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
// Phone3唯一的一个 Class 对象
class Phone3{
    // synchronized 锁的对象是方法的调用者!
    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("打电话");
    }
}

💧案例三总结:两个对象的Class类模板只有一个。static 静态方法在类一加载就有了!锁的是Class。


案例四(先打印“打电话”)

1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?

1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?

public class Test4  {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
// Phone3唯一的一个 Class 对象
class Phone4{
    // 静态的同步方法 锁的是 Class 类模板
    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("打电话");
    }
}

 💧案例四总结:静态的同步方法 锁的是 Class 类模板,普通的同步方法 锁的调用者。

new this 具体的一个手机

static Class 唯一的一个模板

🌊总结一下

   💧在Java并发编程中,8锁现象是指当多个线程对同一个对象的不同锁进行操作时可能导致的性能下降。通过合并锁、细粒度锁、使用并发集合类和并发框架等优化策略,可以避免8锁现象带来的性能问题,提高程序的并发性能和可扩展性。


   💧了解并应用这些优化策略,可以帮助我们编写高效、可靠的并发程序。然而,需要根据具体场景进行分析和调整,避免过度优化和引入新的问题。在实际开发中,综合考虑性能、可维护性和代码可读性,选择适合的优化策略,并进行充分测试和验证。


🐳结语

🐬初学一门技术时,总有些许的疑惑,别怕,它们是我们学习路上的点点繁星,帮助我们不断成长。

🐟积少成多,滴水成河。文章粗浅,希望对大家有帮助!

相关文章
|
8月前
|
Java 编译器
多线程(锁升级, 锁消除, 锁粗化)
多线程(锁升级, 锁消除, 锁粗化)
70 1
|
8月前
|
安全
什么是死锁?互斥锁进入死锁怎么解决?
什么是死锁?互斥锁进入死锁怎么解决?
|
8月前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
98 1
|
8月前
|
安全 算法 Java
Java多线程基础-15:Java 中 synchronized 的优化操作 -- 锁升级、锁消除、锁粗化
`synchronized`在Java并发编程中具有以下特性:开始时是乐观锁,竞争激烈时转为悲观锁;从轻量级锁升级至重量级锁;常使用自旋锁策略;是不公平且可重入的;不支持读写锁。
61 0
|
8月前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
59 4
|
安全 算法 Java
可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用
可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用
|
Java 编译器
Java多线程(4)---死锁和Synchronized加锁流程
Java多线程(4)---死锁和Synchronized加锁流程
79 0
|
8月前
|
Java 编译器 程序员
synchronized 原理(锁升级、锁消除和锁粗化)
synchronized 原理(锁升级、锁消除和锁粗化)
|
监控 安全 Java
【Java并发编程 九】JUC并发包下的锁
【Java并发编程 九】JUC并发包下的锁
73 0
加了一个synchronized锁,程序就崩了
加了一个synchronized锁,程序就崩了