目录
前言
在 Java 中,多线程并发安全是指在多个线程并发访问共享资源时,能确保程序的正确性、可靠性和一致性。为了保证多线程并发安全,Java 提供了多种方法和工具,包括关键字、类、接口等。下面我将详细讲解一些常见的方式。
1. synchronized 关键字
synchronized
关键字用于确保同一时刻只有一个线程能访问某段代码或某个对象的资源,避免多个线程并发访问导致的数据不一致。
使用场景:
-
同步实例方法:锁定当前实例对象
public synchronized void method() { // 代码块 }
-
同步静态方法:锁定类对象
public synchronized static void method() { // 代码块 }
-
同步代码块:锁定指定对象
public void method() { synchronized (lockObject) { // 代码块 } }
注意事项:
- 使用
synchronized
可能会导致线程阻塞,影响性能,特别是在锁竞争激烈时。 synchronized
锁定的是对象实例或类对象,具体依赖于方法的声明(实例方法或静态方法)。
2. Lock 接口(ReentrantLock)
Lock
接口及其常见实现类 ReentrantLock
是 JDK 5 引入的,提供了比 synchronized
更灵活的同步控制。
使用方法:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
}
}
优点:
ReentrantLock
支持可重入锁,即同一线程可以多次获取锁。- 支持条件变量(
Condition
)来控制线程的等待和通知。 - 提供了
tryLock()
方法,可以尝试在一定时间内获取锁,避免死锁。
注意事项:
- 必须显式调用
unlock()
释放锁,否则会发生死锁。
3. volatile 关键字
volatile
关键字用于声明一个变量,它告诉 JVM 不要对这个变量进行缓存,每次访问时都从主内存中读取最新值。volatile
确保了对变量的写操作对所有线程是可见的,但它并不保证操作的原子性。
使用场景:
- 适用于状态标志、线程间的通信、避免缓存问题等。
public class VolatileExample {
private volatile boolean flag = false;
public void method() {
while (!flag) {
// do something
}
}
}
注意事项:
volatile
不能代替synchronized
或Lock
,它不能保证复合操作的原子性。
4. 原子变量(Atomic)类
Java 提供了一些原子类,位于 java.util.concurrent.atomic
包下,例如 AtomicInteger
, AtomicLong
, AtomicReference
等,这些类提供了原子操作,能够保证线程安全地进行常见的数值更新操作。
示例:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet(); // 原子性自增
}
public int getCounter() {
return counter.get(); // 获取当前值
}
}
优点:
- 原子类方法如
incrementAndGet()
等是原子的,保证了操作的线程安全。 - 不需要使用锁,性能更优。
5. ThreadLocal 类
ThreadLocal
是一个线程局部变量,它可以为每个线程提供一个独立的变量副本,使得每个线程操作的变量不会互相干扰,避免了同步问题。
使用方法:
public class ThreadLocalExample {
private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public void method() {
threadLocal.set(threadLocal.get() + 1);
System.out.println(threadLocal.get());
}
}
使用场景:
- 用于保存线程本地数据,比如线程池中每个线程保存自己的状态。
6. CountDownLatch
CountDownLatch
是一个同步辅助类,用于在多个线程之间协调动作。它通过计数器实现,允许一个或多个线程等待,直到计数器的值为零。
示例:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
Runnable task = () -> {
try {
// 模拟任务
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
latch.await(); // 等待计数器减为0
System.out.println("所有线程完成");
}
}
使用场景:
- 用于在多个线程中,控制某些操作的执行时机,直到所有线程都完成时再进行后续操作。
7. CyclicBarrier
CyclicBarrier
是一个同步辅助类,它允许一组线程互相等待,直到所有线程都到达某个公共屏障点,然后所有线程同时继续执行。
示例:
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程已到达屏障点,开始执行后续任务");
});
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " 到达屏障");
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
}
使用场景:
- 用于多个线程并发执行,但希望它们在某个时刻同步到达一起再进行后续操作。
8. 并发集合类
Java 提供了 java.util.concurrent
包下的并发集合类,这些类是为多线程环境设计的,能够保证线程安全。
- CopyOnWriteArrayList:线程安全的 List 实现,适用于读多写少的场景。
- ConcurrentHashMap:线程安全的 Map 实现,支持并发访问。
- BlockingQueue:线程安全的队列,常用于生产者-消费者问题。
9. Executor 框架
Java 提供了线程池框架 Executor
,它能有效管理线程池,避免频繁创建和销毁线程,提升系统性能和资源利用率。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
Runnable task = () -> {
System.out.println(Thread.currentThread().getName() + " 执行任务");
};
for (int i = 0; i < 5; i++) {
executor.submit(task);
}
executor.shutdown();
}
}
总结
- 锁机制:
synchronized
、ReentrantLock
- 线程间通信:
CountDownLatch
、CyclicBarrier
、Semaphore
- 原子性操作:
Atomic
类、volatile
- 线程本地变量:
ThreadLocal
- 并发集合:
CopyOnWriteArrayList
、ConcurrentHashMap
- 线程池:
Executor
框架
根据实际应用场景选择合适的工具和方法。对于高并发场景,尽量减少锁的竞争,使用原子操作和并发集合类,必要时才使用锁。