MST

星途 面试题库

面试题:深入分析Java同步块与方法在不同JVM版本下的实现差异

从JVM底层原理出发,分析Java同步块和同步方法在不同JVM版本(如JDK 7、JDK 8及之后版本)中的实现机制有哪些差异?这些差异对应用程序的并发性能和内存模型会产生怎样的影响?请详细阐述并给出相关代码测试验证思路。
23.1万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

Java同步块和同步方法实现机制差异

  1. JDK 7及之前
    • 同步块(synchronized块):基于监视器(Monitor)实现,Monitor是依赖于底层操作系统的Mutex Lock来实现线程同步。在进入同步块时,线程需要获取Monitor的所有权,这涉及到用户态到内核态的切换,开销较大。
    • 同步方法:本质也是基于Monitor实现。对于实例方法,Monitor与对象实例相关联;对于静态方法,Monitor与类对象相关联。同样存在用户态到内核态切换的开销。
  2. JDK 8及之后
    • 同步块(synchronized块):在偏向锁、轻量级锁等优化机制的加持下,synchronized块的性能得到显著提升。偏向锁适用于只有一个线程多次进入同步块的场景,它会在对象头中记录偏向的线程ID,后续该线程进入时无需再获取锁。轻量级锁则适用于多个线程交替进入同步块的场景,通过CAS操作尝试获取锁,避免了重量级锁的用户态到内核态切换开销。当竞争激烈时,才会升级为重量级锁。
    • 同步方法:同样受益于偏向锁、轻量级锁等优化机制,其底层实现与同步块类似,根据竞争情况在偏向锁、轻量级锁和重量级锁之间进行转换。

对并发性能和内存模型的影响

  1. 并发性能
    • JDK 7及之前:由于频繁的用户态到内核态切换,在高并发场景下,性能较低,线程竞争激烈时,上下文切换开销大。
    • JDK 8及之后:偏向锁和轻量级锁机制减少了锁竞争时的开销,大大提高了并发性能。在低竞争场景下,偏向锁几乎无额外开销;在中等竞争场景下,轻量级锁通过CAS操作快速尝试获取锁,避免了重量级锁的高开销。
  2. 内存模型
    • JDK 7及之前synchronized满足Java内存模型(JMM)的要求,保证了可见性和有序性。线程获取锁时,会将主内存中的变量值刷新到工作内存;释放锁时,会将工作内存中的变量值写回主内存。
    • JDK 8及之后:虽然优化了锁机制,但依然遵循JMM的规则,保证可见性和有序性。偏向锁和轻量级锁的实现并没有破坏JMM的语义,在锁升级为重量级锁时,依然确保内存的可见性和有序性。

代码测试验证思路

  1. 测试并发性能
    • 编写测试类:创建一个包含同步块和同步方法的类。例如:
public class SynchronizationTest {
    private static int counter = 0;

    public static synchronized void syncMethod() {
        counter++;
    }

    public void syncBlock() {
        synchronized (this) {
            counter++;
        }
    }
}
- **多线程并发测试**:使用`ExecutorService`创建多个线程,并发调用同步方法和同步块,统计执行时间。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class SynchronizationPerformanceTest {
    public static void main(String[] args) throws InterruptedException {
        SynchronizationTest test = new SynchronizationTest();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            executorService.submit(() -> {
                test.syncMethod();
                test.syncBlock();
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.MINUTES);
        long endTime = System.currentTimeMillis();
        System.out.println("Total time: " + (endTime - startTime) + " ms");
    }
}
- **对比JDK版本**:分别在JDK 7和JDK 8及以上版本运行上述代码,对比执行时间,观察性能差异。

2. 验证内存模型 - 编写测试类:创建一个类,包含一个共享变量和同步方法,在一个线程中修改共享变量,在另一个线程中读取。例如:

public class MemoryModelTest {
    private static int sharedVariable = 0;

    public static synchronized void writeVariable() {
        sharedVariable = 1;
    }

    public static synchronized int readVariable() {
        return sharedVariable;
    }
}
- **多线程测试**:启动一个线程调用`writeVariable`方法,另一个线程调用`readVariable`方法,验证读取到的值是否为1,以此验证可见性。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MemoryModelVerificationTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.submit(() -> MemoryModelTest.writeVariable());
        executorService.submit(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                int value = MemoryModelTest.readVariable();
                System.out.println("Read value: " + value);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        executorService.shutdown();
        executorService.awaitTermination(2, TimeUnit.MINUTES);
    }
}
- **对比JDK版本**:分别在JDK 7和JDK 8及以上版本运行上述代码,验证内存可见性是否都能得到保证。