ReentrantLock锁时Java5开始引入的java.util.concurrent.locks包中提供的一个锁。
代替了synchronized加锁:
public class Counter {
private final Lock lock = new ReentrantLock();
private int count;
public void add(int n) {
lock.lock();//加
try {
count += n;
} finally {
lock.unlock();//解锁
}
}
}
与synchronized不同的是,ReentrantLock可以尝试加锁,若不成功返回false。则程序可做额外处理,而不是无限等待。
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
...
} finally {
lock.unlock();
}
}
因此使用ReentrantLock更安全。
Condition
使用synchronized可以使线程进入等待和唤醒,而ReentrantLock可以使用Condition使线程进入等待和唤醒。
使用Condition需要创建实例,而实例对象需要锁调用newCondition方法返回获取。
三个方法:
- await():释放锁,进入等待。
- signal():唤醒某个线程。
- signalAll():唤醒所有线程。
class TaskQueue {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private Queue<String> queue = new LinkedList<>();
public void addTask(String s) {
lock.lock();
try {
queue.add(s);
condition.signalAll();
} finally {
lock.unlock();
}
}
public String getTask() {
lock.lock();
try {
while (queue.isEmpty()) {
condition.await();
}
return queue.remove();
} finally {
lock.unlock();
}
}
}
ReadWriteLock
加锁仅仅是解决同步操作带来的写入数据不一致问题,但读取数据进行锁操作的话会降低效率。
但是读取操作不加锁,会造成逻辑不连贯。当某一时刻线程中断,会造成上下数据不一致。
因此出现了ReadWriteLock,它规定了写入锁中的临界区只能被一个线程操作,而读取锁中的临界区可以被任何线程操作。
public class Counter {
private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
private final Lock rlock = rwlock.readLock();
private final Lock wlock = rwlock.writeLock();
private int[] counts = new int[10];
public void inc(int index) {
wlock.lock(); // 加写锁
try {
counts[index] += 1;
} finally {
wlock.unlock(); // 释放写锁
}
}
public int[] get() {
rlock.lock(); // 加读锁
try {
return Arrays.copyOf(counts, counts.length);
} finally {
rlock.unlock(); // 释放读锁
}
}
}
StampedLock
在使用ReadWriteLock有一个问题是:读的时候不允许写。
解决这个问题出现了StampedLock:它包含了悲观锁和乐观锁。
乐观锁的意思就是乐观地估计读的过程中大概率不会有写入,因此被称为乐观锁。反过来,悲观锁则是读的过程中拒绝有写入,也就是写入必须等待。
public class Point {
private final StampedLock stampedLock = new StampedLock();
private double x;
private double y;
public void move(double deltaX, double deltaY) {
long stamp = stampedLock.writeLock(); // 获取写锁
try {
x += deltaX;
y += deltaY;
} finally {
stampedLock.unlockWrite(stamp); // 释放写锁
}
}
public double distanceFromOrigin() {
long stamp = stampedLock.tryOptimisticRead(); // 获得一个乐观读锁
// 注意下面两行代码不是原子操作
// 假设x,y = (100,200)
double currentX = x;
// 此处已读取到x=100,但x,y可能被写线程修改为(300,400)
double currentY = y;
// 此处已读取到y,如果没有写入,读取是正确的(100,200)
// 如果有写入,读取是错误的(100,400)
if (!stampedLock.validate(stamp)) { // 检查乐观读锁后是否有其他写锁发生
stamp = stampedLock.readLock(); // 获取一个悲观读锁
try {
currentX = x;
currentY = y;
} finally {
stampedLock.unlockRead(stamp); // 释放悲观读锁
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}