MST

星途 面试题库

面试题:Java同步块与方法在性能优化方面的考量

在高并发场景下,Java的同步块和同步方法在性能优化上需要注意哪些要点?比如如何选择锁的粒度、锁的类型对性能有什么影响?请详细分析并结合代码示例说明。
37.3万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. 锁的粒度选择

  • 粗粒度锁
    • 分析:粗粒度锁会锁定较大范围的代码块或方法,在高并发场景下,会导致大量线程竞争同一把锁,造成线程阻塞,降低系统并发性能。但如果锁保护的代码执行时间较长,粗粒度锁可能减少加锁解锁的开销。
    • 示例
public class CoarseGrainedLockExample {
    private static final Object lock = new Object();
    public void doSomething() {
        synchronized (lock) {
            // 执行一系列操作,可能包含多个独立操作,但都被锁保护
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 1000000; i++) {
                // 模拟一些计算
            }
            long endTime = System.currentTimeMillis();
            System.out.println("Coarse grained lock operation took " + (endTime - startTime) + " ms");
        }
    }
}
  • 细粒度锁
    • 分析:细粒度锁只锁定关键的、可能产生竞争的代码片段,能提高并发性能,因为不同线程可以同时访问不同的细粒度锁保护的区域。但如果锁的粒度太细,加锁解锁的开销可能会增加。
    • 示例
public class FineGrainedLockExample {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();
    public void doPart1() {
        synchronized (lock1) {
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 500000; i++) {
                // 模拟一些计算
            }
            long endTime = System.currentTimeMillis();
            System.out.println("Part 1 with fine grained lock took " + (endTime - startTime) + " ms");
        }
    }
    public void doPart2() {
        synchronized (lock2) {
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 500000; i++) {
                // 模拟一些计算
            }
            long endTime = System.currentTimeMillis();
            System.out.println("Part 2 with fine grained lock took " + (endTime - startTime) + " ms");
        }
    }
}

2. 锁的类型对性能的影响

  • 内置锁(synchronized)
    • 分析:synchronized 是Java内置的同步机制,它是一种悲观锁,即总是假设最坏的情况,每次获取锁时都认为其他线程会竞争,所以会进行加锁操作。在JDK 1.6 之前性能较差,因为竞争激烈时会导致线程频繁阻塞和唤醒。但在JDK 1.6 之后,引入了偏向锁、轻量级锁等优化机制,性能有了较大提升。
    • 示例
public class SynchronizedExample {
    public synchronized void syncMethod() {
        // 同步方法
    }
    public void syncBlock() {
        synchronized (this) {
            // 同步块
        }
    }
}
  • ReentrantLock
    • 分析:ReentrantLock 是Java.util.concurrent.locks包下的显式锁,它提供了比 synchronized 更灵活的锁控制,比如可中断的获取锁、公平锁与非公平锁的选择等。在竞争激烈的场景下,ReentrantLock 的性能可能优于 synchronized,因为它可以使用非公平锁来减少线程切换的开销,但如果需要公平性,公平锁会增加性能开销。
    • 示例
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
    private static final ReentrantLock lock = new ReentrantLock();
    public void doWork() {
        lock.lock();
        try {
            // 业务逻辑
        } finally {
            lock.unlock();
        }
    }
}
  • 读写锁(ReentrantReadWriteLock)
    • 分析:适用于读多写少的场景。它将锁分为读锁和写锁,允许多个线程同时获取读锁,但只允许一个线程获取写锁。读锁之间不互斥,提高了读操作的并发性能。如果写操作频繁,可能会导致读线程长时间等待。
    • 示例
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
    private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    private static final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    public void readData() {
        readLock.lock();
        try {
            // 读操作
        } finally {
            readLock.unlock();
        }
    }
    public void writeData() {
        writeLock.lock();
        try {
            // 写操作
        } finally {
            writeLock.unlock();
        }
    }
}

3. 其他要点

  • 减少锁的持有时间:尽量将不需要锁保护的代码移出同步块或同步方法,减少线程持有锁的时间,从而降低锁竞争。
  • 避免死锁:在使用多个锁时,要按照一定的顺序获取锁,避免形成死锁。例如在两个线程分别获取锁A和锁B时,如果都试图获取对方持有的锁,就可能死锁。可以规定统一的获取锁顺序(如先获取锁A再获取锁B)来避免这种情况。