1. 前序
在多线程编程中,线程同步是确保数据一致性和防止竞态条件的关键。Java 提供了多种用于线程同步的机制,以解决不同场景下的线程竞争问题。无论是最基本的 synchronized
关键字,还是更灵活的 ReentrantLock
、ReentrantReadWriteLock
,它们都为开发者提供了不同级别的锁和控制。
本文将逐一介绍 Java 中常见的同步机制,涵盖了 synchronized
、ReentrantLock
、Atomic
类等,同时给出每种机制的示例代码和适用场景,帮助你更好地理解并应用这些同步机制。
2. synchronized
2.1 synchronized
关键字
- 描述:Java 中最基础的同步机制就是
synchronized
关键字,它可以用于方法或代码块,确保同一时刻只有一个线程可以访问共享资源。 - 示例代码:
/**
* 示例类,演示如何使用 synchronized 方法进行线程同步
*/
public class SynchronizedMethodExample {
/** 共享计数器 */
private int counter = 0;
/**
* 同步递增计数器的方法,确保同一时刻只有一个线程可以执行
*/
public synchronized void increment() {
// 递增计数器
counter++;
// 输出当前线程和计数器的值
System.out.println(Thread.currentThread().getName() + " - Counter: " + counter);
}
/**
* 主程序入口,创建多个线程并运行
* @param args 默认参数
*/
public static void main(String[] args) {
SynchronizedMethodExample example = new SynchronizedMethodExample();
// 线程任务,调用 increment 方法
Runnable task = example::increment;
// 创建两个线程
Thread t1 = new Thread(task, "Thread 1");
Thread t2 = new Thread(task, "Thread 2");
// 启动线程
t1.start();
t2.start();
}
}
2.2 使用 synchronized
代码块
- 描述:相比于
synchronized
方法,synchronized
代码块允许更细粒度地控制同步范围。可以指定特定的代码块进行同步,而不是整个方法,这样可以减少锁的竞争,提高效率。 - 示例代码:
/**
* 示例类,演示如何使用 synchronized 代码块进行线程同步
*/
public class SynchronizedBlockExample {
/** 共享计数器 */
private int counter = 0;
/** 自定义锁对象 */
private final Object lock = new Object();
/**
* 同步递增计数器的方法,只锁定代码块
*/
public void increment() {
// 使用 synchronized 代码块确保锁定的粒度更小
synchronized (lock) {
counter++;
System.out.println(Thread.currentThread().getName() + " - Counter: " + counter);
}
}
/**
* 主程序入口,创建多个线程并运行
* @param args 默认参数
*/
public static void main(String[] args) {
SynchronizedBlockExample example = new SynchronizedBlockExample();
Runnable task = example::increment;
Thread t1 = new Thread(task, "Thread 1");
Thread t2 = new Thread(task, "Thread 2");
t1.start();
t2.start();
}
}
3. ReentrantLock
- 描述:
ReentrantLock
是Lock
接口的一个常用实现,它提供了更灵活的锁定机制,允许手动加锁和解锁。 - 示例代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 示例类,演示如何使用 ReentrantLock 进行线程同步
*/
public class LockExample {
/** 共享计数器 */
private int counter = 0;
/** ReentrantLock 实例 */
private final Lock lock = new ReentrantLock();
/**
* 同步递增计数器的方法,手动加锁和解锁
*/
public void increment() {
// 获取锁
lock.lock();
try {
// 递增计数器
counter++;
// 输出当前线程和计数器的值
System.out.println(Thread.currentThread().getName() + " - Counter: " + counter);
} finally {
// 确保锁在最后被释放
lock.unlock();
}
}
/**
* 主程序入口,创建多个线程并运行
* @param args 默认参数
*/
public static void main(String[] args) {
LockExample example = new LockExample();
Runnable task = example::increment;
Thread t1 = new Thread(task, "Thread 1");
Thread t2 = new Thread(task, "Thread 2");
t1.start();
t2.start();
}
}
4. ReentrantReadWriteLock
- 描述:
ReentrantReadWriteLock
提供了读写锁机制,可以让多个线程并发读取,但在写入时只有一个线程可以操作。这样可以提高在读多写少场景下的性能。 - 示例代码:
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 示例类,演示如何使用 ReentrantReadWriteLock 进行线程同步
*/
public class ReadWriteLockExample {
/** 共享计数器 */
private int counter = 0;
/** ReentrantReadWriteLock 实例 */
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
/**
* 获取写锁并递增计数器
*/
public void increment() {
// 获取写锁
lock.writeLock().lock();
try {
// 递增计数器
counter++;
System.out.println(Thread.currentThread().getName() + " - Write Counter: " + counter);
} finally {
// 释放写锁
lock.writeLock().unlock();
}
}
/**
* 获取读锁并读取计数器
* @return 计数器值
*/
public int getCounter() {
// 获取读锁
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " - Read Counter: " + counter);
return counter;
} finally {
// 释放读锁
lock.readLock().unlock();
}
}
/**
* 主程序入口,创建多个线程并运行
* @param args 默认参数
*/
public static void main(String[] args) {
ReadWriteLockExample example = new ReadWriteLockExample();
Runnable writeTask = example::increment;
Runnable readTask = example::getCounter;
Thread t1 = new Thread(writeTask, "Writer Thread");
Thread t2 = new Thread(readTask, "Reader Thread");
t1.start();
t2.start();
}
}
5. Atomic
类
- 描述:
Atomic
类位于java.util.concurrent.atomic
包内,提供了常见的原子操作类(如AtomicInteger
),用于在无锁的情况下对单一变量进行线程安全的操作。 - 示例代码:
import java.util.concurrent.atomic.AtomicInteger;
/**
* 示例类,演示如何使用 AtomicInteger 进行线程同步
*/
public class AtomicExample {
/** 线程安全的 AtomicInteger */
private AtomicInteger counter = new AtomicInteger(0);
/**
* 原子性递增计数器的方法
*/
public void increment() {
// 原子递增
int newValue = counter.incrementAndGet();
System.out.println(Thread.currentThread().getName() + " - Counter: " + newValue);
}
/**
* 主程序入口,创建多个线程并运行
* @param args 默认参数
*/
public static void main(String[] args) {
AtomicExample example = new AtomicExample();
Runnable task = example::increment;
Thread t1 = new Thread(task, "Thread 1");
Thread t2 = new Thread(task, "Thread 2");
t1.start();
t2.start();
}
}
6. CyclicBarrier
- 描述:
CyclicBarrier
是一种允许一组线程相互等待的同步机制,直到所有线程都到达某个共同的屏障点时,才能继续执行。它支持重用,即可以被多次使用。 - 示例代码:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 示例类,演示如何使用 CyclicBarrier 实现线程同步
*/
public class CyclicBarrierExample {
/** CyclicBarrier 实例,等待 3 个线程 */
private final CyclicBarrier barrier = new CyclicBarrier(3, () -> {
// 所有线程到达屏障后执行的操作
System.out.println("All threads have reached the barrier. Barrier action executed.");
});
/**
* 线程任务,等待屏障点并继续执行
*/
public void performTask() {
System.out.println(Thread.currentThread().getName() + " is waiting at the barrier");
try {
// 等待其他线程到达屏障点
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " has crossed the barrier");
}
/**
* 主程序入口,创建多个线程并运行
* @param args 默认参数
*/
public static void main(String[] args) {
CyclicBarrierExample example = new CyclicBarrierExample();
Thread t1 = new Thread(example::performTask, "Thread 1");
Thread t2 = new Thread(example::performTask, "Thread 2");
Thread t3 = new Thread(example::performTask, "Thread 3");
t1.start();
t2.start();
t3.start();
}
}
7. Object
的 wait()
和 notify()
方法
- 描述:
Object
类的wait()
、notify()
和notifyAll()
方法允许线程在某个条件下进行等待和唤醒。与synchronized
搭配使用,可以实现类似于信号量的功能。 - 示例代码:
/**
* 示例类,演示如何使用 wait() 和 notify() 进行线程同步
*/
public class WaitNotifyExample {
/** 自定义锁对象 */
private final Object lock = new Object();
/** 标志位,表示是否已经生产了数据 */
private boolean isProduced = false;
/**
* 生产者方法,等待消费者消费后生产新数据
* @throws InterruptedException 当线程被中断时抛出异常
*/
public void produce() throws InterruptedException {
synchronized (lock) {
// 如果已经生产了数据,等待消费者消费
while (isProduced) {
lock.wait();
}
// 生产数据
System.out.println(Thread.currentThread().getName() + " produced data.");
isProduced = true;
// 通知消费者可以消费数据了
lock.notify();
}
}
/**
* 消费者方法,等待生产者生产数据并进行消费
* @throws InterruptedException 当线程被中断时抛出异常
*/
public void consume() throws InterruptedException {
synchronized (lock) {
// 如果还没有生产数据,等待生产者生产
while (!isProduced) {
lock.wait();
}
// 消费数据
System.out.println(Thread.currentThread().getName() + " consumed data.");
isProduced = false;
// 通知生产者可以继续生产数据了
lock.notify();
}
}
/**
* 主程序入口,创建生产者和消费者线程并运行
* @param args 默认参数
*/
public static void main(String[] args) {
WaitNotifyExample example = new WaitNotifyExample();
// 创建生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
example.produce();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Producer");
// 创建消费者线程
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
example.consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Consumer");
producer.start();
consumer.start();
}
}
8. 总结
Java 提供了多种用于线程同步的机制,包括 synchronized
、ReentrantLock
、ReentrantReadWriteLock
、Atomic
类、CyclicBarrier
以及 Object
的 wait()
/notify()
。每种方式都有其适用场景和优缺点。对于简单的同步需求,synchronized
是一种直接而有效的选择;对于复杂的并发控制,Lock
提供了更灵活的锁机制;而 wait()
和 notify()
可以实现线程之间的协调工作。