目录
7. 使用 CountDownLatch 和 CyclicBarrier
1. 使用 synchronized
关键字
synchronized
是 Java 中最常用的同步机制,它可以用于方法或者代码块。它通过使用对象锁来确保只有一个线程能够访问同步块或同步方法。
1.1 同步实例方法
当一个实例方法使用 synchronized
修饰时,锁的是当前实例对象 this
。
public class Counter {
private int count = 0;
// 使用 synchronized 修饰实例方法,确保每次只有一个线程能调用此方法
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
在上面的例子中,increment()
和 getCount()
方法是同步的,多个线程同时访问时,只有一个线程能进入这些方法,其他线程需要等待。
1.2 同步静态方法
当一个静态方法使用 synchronized
修饰时,锁的是类的 Class
对象,而不是实例对象。
public class Counter {
private static int count = 0;
// 使用 synchronized 修饰静态方法,锁的是 Counter.class
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
在这种情况下,所有线程都会被同一个类的锁(Counter.class
)同步,所以即使是不同的实例,它们也会争抢同一个类的锁。
1.3 同步代码块
如果你只想同步方法中的一部分代码,可以使用同步代码块,指定一个对象作为锁对象。通常,synchronized
用于局部同步,锁定更细粒度的资源。
public class Counter {
private int count = 0;
public void increment() {
synchronized (this) { // 锁定当前实例
count++;
}
}
public int getCount() {
synchronized (this) { // 锁定当前实例
return count;
}
}
}
在这种情况下,synchronized
只会锁定方法体中的一部分,而不是整个方法,这样可以减少同步的开销。
2. 使用 ReentrantLock
(显示锁)
ReentrantLock
是 java.util.concurrent.locks
包中的一个类,提供了比 synchronized
更加灵活和强大的同步机制。它显式地获取和释放锁,相较于 synchronized
更加灵活,允许进行尝试锁定和中断锁定等操作。
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 确保释放锁
}
}
public int getCount() {
lock.lock(); // 获取锁
try {
return count;
} finally {
lock.unlock(); // 确保释放锁
}
}
}
3. 使用 ReadWriteLock
ReadWriteLock
是一种更为精细的锁,它允许多个线程同时读取共享资源,但在写操作时会阻塞所有的读线程和写线程。ReentrantReadWriteLock
是 ReadWriteLock
的常见实现。
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Counter {
private int count = 0;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void increment() {
lock.writeLock().lock(); // 获取写锁
try {
count++;
} finally {
lock.writeLock().unlock(); // 释放写锁
}
}
public int getCount() {
lock.readLock().lock(); // 获取读锁
try {
return count;
} finally {
lock.readLock().unlock(); // 释放读锁
}
}
}
4. 使用 volatile
关键字
volatile
关键字确保变量的可见性。即,当一个线程修改了 volatile
变量的值,其他线程能够立即看到修改后的值。volatile
并不提供互斥的锁定,因此不能保证原子性,但它确保了数据的最新值被共享。
public class Counter {
private volatile int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
5. 使用 Atomic
类 (原子变量)
Java 提供了 java.util.concurrent.atomic
包中的一组类,如 AtomicInteger
、AtomicLong
、AtomicReference
等,它们通过 CAS(比较并交换)机制提供了线程安全的操作。
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子性操作
}
public int getCount() {
return count.get(); // 获取原子值
}
}
6. 使用 Semaphore
(信号量)
Semaphore
是一个计数信号量,可以控制同时访问某个特定资源的线程数量。它在一定程度上提供了一种高级的同步机制,可以用于实现限流等功能。
import java.util.concurrent.Semaphore;
public class Resource {
private final Semaphore semaphore = new Semaphore(1); // 只有一个许可
public void accessResource() throws InterruptedException {
semaphore.acquire(); // 获取许可
try {
// 访问共享资源
} finally {
semaphore.release(); // 释放许可
}
}
}
7. 使用 CountDownLatch
和 CyclicBarrier
这两种类用于线程之间的协调,帮助实现线程的同步。
CountDownLatch
:常用于一个线程等待其他线程完成后再继续执行。CyclicBarrier
:允许多个线程互相等待,直到所有线程都达到某个公共屏障点
import java.util.concurrent.CountDownLatch;
public class Example {
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");
latch.countDown();
}).start();
}
latch.await(); // 主线程等待
System.out.println("All threads finished");
}
}
总结
在 Java 中,常见的线程同步方法包括:
synchronized
关键字:最常用的同步方法,可以用于同步方法和同步代码块。ReentrantLock
:显示锁,提供比synchronized
更强大的功能。ReadWriteLock
:用于处理读写锁,适合频繁读少量写的场景。volatile
关键字:保证变量的可见性,适用于不需要原子性的场景。Atomic
类:通过 CAS(比较并交换)实现原子性操作。Semaphore
:用于控制并发访问的线程数。CountDownLatch
和CyclicBarrier
:用于线程协调。
选择不同的同步机制取决于应用场景的需求和性能考虑。