文章目录
一、线程同步概述1.1 为什么需要线程同步1.2 线程同步的基本概念
二、synchronized关键字2.1 synchronized的三种使用方式2.1.1 同步实例方法2.1.2 同步静态方法2.1.3 同步代码块
2.2 synchronized原理分析2.3 synchronized示例
三、显式锁(Lock接口)3.1 ReentrantLock3.2 ReentrantLock vs synchronized3.3 读写锁(ReentrantReadWriteLock)
四、其他同步机制4.1 volatile关键字4.2 原子变量(Atomic Classes)4.3 ThreadLocal
五、高级同步工具5.1 CountDownLatch5.2 CyclicBarrier5.3 Semaphore
六、死锁与避免策略6.1 死锁示例6.2 死锁避免策略
七、性能考虑与最佳实践7.1 同步性能优化7.2 最佳实践
八、总结
一、线程同步概述
1.1 为什么需要线程同步
在多线程编程中,当多个线程同时访问共享资源时,可能会出现数据不一致的问题。这是因为线程的执行顺序是不确定的,可能会出现以下情况:
竞态条件(Race Condition):多个线程同时读写共享数据,导致最终结果依赖于线程执行的时序数据不一致:一个线程正在修改数据时,另一个线程读取了中间状态的数据内存可见性问题:一个线程对共享变量的修改可能不会立即对其他线程可见
1.2 线程同步的基本概念
线程同步是指协调多个线程对共享资源的访问,确保在任何时刻只有一个线程可以访问特定的共享资源。Java提供了多种同步机制:
内置锁(synchronized)显式锁(Lock接口)原子变量volatile关键字线程安全容器CountDownLatch/CyclicBarrier等同步工具
二、synchronized关键字
2.1 synchronized的三种使用方式
2.1.1 同步实例方法
public synchronized void increment() {
// 方法体
}
2.1.2 同步静态方法
public static synchronized void staticIncrement() {
// 方法体
}
2.1.3 同步代码块
public void method() {
// 非同步代码
synchronized(this) { // 或者 synchronized(SomeClass.class) 对于静态同步
// 同步代码块
}
// 非同步代码
}
2.2 synchronized原理分析
synchronized是基于JVM内置的监视器锁(Monitor)实现的。每个Java对象都有一个与之关联的Monitor,当线程进入synchronized块时:
尝试获取对象的Monitor如果获取成功,继续执行同步代码如果获取失败,线程进入阻塞状态,直到Monitor被释放
2.3 synchronized示例
public class SynchronizedCounter {
private int count = 0;
// 同步实例方法
public synchronized void increment() {
count++;
}
// 同步静态方法
public static synchronized void staticIncrement() {
// 静态同步代码
}
// 同步代码块
public void doSomething() {
// 非同步代码
synchronized(this) {
// 同步代码
if(count > 10) {
count = 0;
}
}
// 非同步代码
}
}
三、显式锁(Lock接口)
3.1 ReentrantLock
ReentrantLock是Java提供的显式锁实现,提供了比synchronized更灵活的锁操作。
public class LockCounter {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 确保锁被释放
}
}
}
3.2 ReentrantLock vs synchronized
特性synchronizedReentrantLock实现方式JVM内置Java代码实现获取锁超时不支持支持可中断不支持支持公平锁非公平可配置公平/非公平条件变量有限支持完全支持性能JDK6后优化通常更高
3.3 读写锁(ReentrantReadWriteLock)
读写锁允许多个读操作同时进行,但写操作是独占的。
public class ReadWriteCache {
private final Map
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public Object get(String key) {
rwLock.readLock().lock();
try {
return cache.get(key);
} finally {
rwLock.readLock().unlock();
}
}
public void put(String key, Object value) {
rwLock.writeLock().lock();
try {
cache.put(key, value);
} finally {
rwLock.writeLock().unlock();
}
}
}
四、其他同步机制
4.1 volatile关键字
volatile确保变量的可见性和禁止指令重排序,但不保证原子性。
public class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写volatile变量
}
public void reader() {
if(flag) { // 读volatile变量
// 执行操作
}
}
}
4.2 原子变量(Atomic Classes)
Java提供了一系列原子变量类,如AtomicInteger、AtomicLong等。
public class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
4.3 ThreadLocal
ThreadLocal为每个线程提供独立的变量副本,避免共享。
public class ThreadLocalExample {
private static ThreadLocal
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return dateFormat.get().format(date);
}
}
五、高级同步工具
5.1 CountDownLatch
允许一个或多个线程等待其他线程完成操作。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for(int i = 0; i < 3; i++) {
new Thread(() -> {
// 执行任务
latch.countDown();
}).start();
}
latch.await(); // 等待所有线程完成
System.out.println("所有任务已完成");
}
}
5.2 CyclicBarrier
让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,所有线程才会继续执行。
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程已到达屏障点");
});
for(int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 执行第一阶段任务
barrier.await();
// 执行第二阶段任务
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
5.3 Semaphore
控制同时访问特定资源的线程数量。
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 允许3个线程同时访问
for(int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
// 访问共享资源
Thread.sleep(1000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
六、死锁与避免策略
6.1 死锁示例
public class DeadlockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized(lock1) {
System.out.println("Thread1持有lock1");
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(lock2) {
System.out.println("Thread1持有lock1和lock2");
}
}
}).start();
new Thread(() -> {
synchronized(lock2) {
System.out.println("Thread2持有lock2");
synchronized(lock1) {
System.out.println("Thread2持有lock1和lock2");
}
}
}).start();
}
}
6.2 死锁避免策略
避免嵌套锁:尽量不要在一个同步块中获取多个锁锁排序:如果必须获取多个锁,确保所有线程以相同的顺序获取锁使用tryLock:尝试获取锁并设置超时时间使用更高级的并发工具:如ConcurrentHashMap等
七、性能考虑与最佳实践
7.1 同步性能优化
减小同步范围:只在必要时同步关键代码使用读写锁:读多写少的场景下性能更好考虑并发容器:如ConcurrentHashMap、CopyOnWriteArrayList等避免过早优化:先保证正确性,再考虑性能
7.2 最佳实践
明确同步策略:文档化类的线程安全保证优先使用现有的线程安全类:如AtomicInteger等避免在同步块中调用外部方法:可能导致死锁或性能问题考虑使用不可变对象:避免同步需求谨慎使用双重检查锁定:在Java 5+中可以使用volatile正确实现
八、总结
Java提供了丰富的线程同步机制,从基本的synchronized关键字到高级的并发工具类。选择合适的同步机制需要考虑应用场景、性能需求和复杂性。理解这些同步机制的原理和适用场景,是编写正确、高效并发程序的关键。
在实际开发中,应当:
优先考虑使用现有的线程安全类尽量减小同步范围文档化类的线程安全保证在复杂场景下考虑使用java.util.concurrent包中的高级工具
通过合理使用这些同步机制,可以构建出既安全又高效的并发应用程序。