各种锁
Java中提供了多种锁机制来帮助开发者解决多线程环境下的线程安全问题。以下是一些Java中常见的锁:
-
synchronized关键字:
- 同步方法:直接在方法上加上
synchronized
关键字,使得整个方法在执行时,必须获得对象的锁。 - 同步块:对方法中的特定代码段进行同步,使用
synchronized(对象)
{},只对需要同步的代码部分进行锁定。
- 同步方法:直接在方法上加上
-
volatile关键字:
- 用于确保变量修改的可见性和顺序性,但不保证原子性。它适合作为状态标志使用,但不适合复合状态的同步。
-
ReentrantLock(可重入锁):
- 实现了
Lock
接口,拥有与synchronized
相同的并发性和内存语义,但提供了更高的扩展性。 - 支持公平锁和非公平锁,默认是非公平锁。
- 支持中断正在等待获取锁的线程,支持多个条件变量(Condition)。
- 实现了
-
ReadWriteLock(读写锁):
- 读写锁维护了一对相关的锁:一个用于只读操作,另一个用于写操作。
- 读锁可以由多个线程同时持有,只要没有线程持有写锁即可。
- 写锁是独占的。
-
StampedLock:
- 是
ReadWriteLock
的一个改进版本,提供了乐观读锁的功能。 - 乐观读锁允许在数据没有被其他线程修改的情况下进行多次读取,从而减少了锁的争用。
- 是
-
CountDownLatch(倒计时锁):
- 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
-
CyclicBarrier(循环屏障):
- 一个同步辅助类,它允许一组线程互相等待,达到一个共同的屏障点后再继续执行。
-
Semaphore(信号量):
- 一个计数信号量,用于管理一组资源,它通过协调各个线程,以保证合理的使用公共资源。
-
Exchanger(交换者):
- 一个同步辅助类,允许两个线程在到达某个汇合点时交换数据。
每种锁都有其特定的使用场景,选择合适的锁机制对于提高多线程程序的性能和可靠性至关重要。
在Java中,多种锁机制用于解决多线程环境下的线程安全问题。以下通过示例来讲解几种常见的锁的使用:
示例演示
1. synchronized关键字
synchronized是Java中最基本的同步机制,可以用于方法或代码块。
示例:同步方法
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public synchronized int getCount() {
return count;
}
}
在这个例子中,increment
、decrement
和getCount
方法都被synchronized修饰,这意味着在任意时刻,只有一个线程能执行这三个方法中的任何一个。
示例:同步代码块
public class Counter {
private final Object lock = new Object();
private int count = 0;
public void increment() {
synchronized (lock) {
count++;
}
}
// decrement和getCount方法也可以使用相同的lock对象进行同步
}
这里,我们使用了一个显式的锁对象lock
来同步increment
方法中的代码块。
2. ReentrantLock
ReentrantLock是一个可重入的互斥锁,具有与synchronized关键字类似的同步功能,但提供了更高的灵活性和扩展性。
示例:使用ReentrantLock
import java.util.concurrent.locks.ReentrantLock;
public class CounterWithLock {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
// decrement和getCount方法也可以使用相同的lock对象进行同步
}
在这个例子中,我们使用ReentrantLock来同步对count
变量的访问。注意,在finally块中释放锁是非常重要的,以确保即使在发生异常时也能释放锁。
3. ReadWriteLock
ReadWriteLock是一个读写锁接口,它允许一个或多个读线程同时访问共享资源,但写线程是独占的。
示例:使用ReentrantReadWriteLock
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.Lock;
public class DataContainer {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private int data;
public void setData(int value) {
writeLock.lock();
try {
data = value;
} finally {
writeLock.unlock();
}
}
public int getData() {
readLock.lock();
try {
return data;
} finally {
readLock.unlock();
}
}
}
在这个例子中,我们使用ReentrantReadWriteLock来实现读写锁。写操作通过writeLock
进行同步,而读操作通过readLock
进行同步。这允许多个读线程同时访问data
变量,但写线程是独占的。
4. CountDownLatch
CountDownLatch是一个同步辅助类,用于让一个或多个线程等待直到其他线程完成一系列操作。
示例:使用CountDownLatch
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int n = 5;
CountDownLatch latch = new CountDownLatch(n);
for (int i = 0; i < n; i++) {
new Thread(() -> {
try {
// 模拟任务执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown(); // 任务完成,计数器减一
}).start();
}
latch.await(); // 等待所有任务完成
System.out.println("All tasks done");
}
}
在这个例子中,我们创建了一个CountDownLatch实例,初始计数为5。
然后启动了5个线程来模拟任务的执行,每个线程在执行完任务后调用countDown()
方法将计数器减一。
主线程调用await()
方法等待,直到计数器变为0,表示所有任务都已完成。
这些示例展示了Java中几种常见锁的使用场景和方式,有助于理解多线程同步机制的应用。
欢迎访问我的(夏壹分享)公众号 和 博客(sanzhiwa)后缀top