java中的线程同步
在 Java 中,线程同步是指在多线程环境中协调多个线程的执行,确保共享资源在同一时刻只被一个线程访问,避免并发问题如数据不一致、竞态条件等。Java 提供了多种机制来实现线程同步,主要包括 synchronized
关键字、显式锁(ReentrantLock
)、以及其他并发工具类。
1. synchronized
关键字
synchronized
是 Java 中最基本的同步机制。它可以用于方法或代码块,确保同一时刻只有一个线程可以执行被同步的代码。
1.1 同步方法
Synchronized
可以用于实例方法和静态方法,保证同一对象的多个线程访问时,只有一个线程能进入同步方法。
同步实例方法:对对象实例加锁,多个线程访问同一个对象时,只有一个线程能执行同步方法。
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
同步静态方法:对类加锁,所有线程都会共享同一个类锁,确保类级别的同步。
class Counter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
1.2 同步代码块
相比同步整个方法,同步代码块可以将锁的范围缩小,只对特定的代码块加锁,提升效率。
class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
}
锁对象:
synchronized
可以锁定任何对象,通常锁定this
或者某个共享资源对象。
2. 显式锁 (ReentrantLock
)
ReentrantLock
是 Java 提供的显式锁,相比 synchronized
更加灵活,具备了可重入、可中断、超时获取锁等特性。它是 java.util.concurrent.locks
包的一部分。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 加锁
try {
count++;
} finally {
lock.unlock(); // 确保最终释放锁
}
}
public int getCount() {
return count;
}
}
lock.lock()
:加锁,直到获得锁后才能继续执行。lock.unlock()
:释放锁,确保释放锁在finally
块中,以免线程发生异常时无法释放锁。
2.1 ReentrantLock
其他功能
公平锁和非公平锁:通过构造函数可以设置是否使用公平锁(公平锁会让等待时间最长的线程优先获取锁)。
Lock lock = new ReentrantLock(true); // 公平锁
可中断获取锁:
lock.lockInterruptibly()
允许线程在等待锁的过程中响应中断。
lock.lockInterruptibly();
超时获取锁:
tryLock(long timeout, TimeUnit unit)
可以指定获取锁的超时时间,避免线程无限等待。
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// 执行临界区代码
} finally {
lock.unlock();
}
}
3. volatile
关键字
volatile
用于确保变量的可见性,它能保证当一个线程修改某个 volatile
变量的值时,其他线程能够立即看到这个变化。volatile
不保证操作的原子性,但保证内存的可见性,适合对简单状态变量的读写操作。
class MyRunnable implements Runnable {
private volatile boolean running = true;
public void run() {
while (running) {
System.out.println("Thread is running");
}
}
public void stop() {
running = false; // 其他线程可以看到这个修改
}
}
4. 线程间通信:wait()
、notify()
和 notifyAll()
Java 提供了 wait()
、notify()
和 notifyAll()
三个方法,用于线程之间的通信,主要用于多线程之间的协调。它们只能在同步代码块或同步方法中调用,确保锁的机制配合通信使用。
4.1 wait()
和 notify()
wait()
:让当前线程进入等待状态,释放锁并等待其他线程调用notify()
或notifyAll()
来唤醒它。notify()
:唤醒一个等待线程。notifyAll()
:唤醒所有等待线程。
class SharedResource {
private boolean available = false;
public synchronized void produce() throws InterruptedException {
while (available) {
wait(); // 等待消费者消费
}
available = true;
System.out.println("Produced an item");
notify(); // 通知消费者可以消费了
}
public synchronized void consume() throws InterruptedException {
while (!available) {
wait(); // 等待生产者生产
}
available = false;
System.out.println("Consumed an item");
notify(); // 通知生产者可以生产了
}
}
4.2 使用 wait()
和 notify()
的注意点
必须在
synchronized
方法或代码块中调用。wait()
会释放锁,但notify()
或notifyAll()
只是唤醒线程,并不会立即释放锁。
5. 并发工具类
Java 提供了一些高级的并发工具类,帮助简化线程同步和线程间通信。
5.1 CountDownLatch
CountDownLatch
用于让一个或多个线程等待其他线程完成某些操作。它有一个计数器,每当线程完成一个操作时,计数器减 1,当计数器为 0 时,所有等待的线程被唤醒。
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("Thread finished task");
latch.countDown(); // 每个线程完成任务后调用
}).start();
}
latch.await(); // 主线程等待所有线程完成任务
System.out.println("All tasks finished");
}
}
5.2 CyclicBarrier
CyclicBarrier
类似于 CountDownLatch
,但它让一组线程等待彼此,直到所有线程都达到某个屏障点之后,才可以继续执行。这适用于并行计算任务阶段性汇合。
import java.util.concurrent.CyclicBarrier;
public class Main {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads reached the barrier");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " reached the barrier");
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
5.3 Semaphore
Semaphore
用于控制同时访问特定资源的线程数量。例如,控制对某个共享资源的最大并发访问数。
import java.util.concurrent.Semaphore;
public class Main {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 允许3个线程同时访问
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " got the permit");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
总结
Java 提供了多种线程同步机制,synchronized
是最基础的同步方法,适合简单的同步场景。而显式锁(ReentrantLock
)为复杂场景提供了更多功能。同时,并发工具类如 CountDownLatch
、CyclicBarrier
和 Semaphore
也为常见的并发问题提供了解决方案。在选择线程同步机制时,应根据应用的具体需求来平衡性能和可维护性。