副标题#e#
独占锁的实现,拥有上面列举的除读写锁之外的所有特性,使用比较简单
class X {
// 创建独占锁实例
private final ReentrantLock lock = new ReentrantLock();
// …
public void m() {
lock.lock(); // block until condition holds
try {
// … method body
} finally {
// 必须要释放锁,unlock与lock成对出现
lock.unlock()
}
}
}
ReentrantReadWriteLock
读写锁的实现,拥有上面列举的所有特性。并且写锁可降级为读锁,反之不行。
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = …
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
StampedLock
StampedLock也是一种读写锁,提供两种读模式:乐观读和悲观读。乐观读允许读的过程中也可以获取写锁后写入!这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入。
#p#副标题#e#
乐观锁的意思就是乐观地估计读的过程中大概率不会有写入,因此被称为乐观锁。反过来,悲观锁则是读的过程中拒绝有写入,也就是写入必须等待。显然乐观锁的并发效率更高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行。
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);
}
}
Condition#p#副标题#e##p#分页标题#e#
Condition成为条件队列或条件变量,为一个线程挂起执行(等待)提供了一种方法,直到另一线程通知某些状态条件现在可能为真为止。由于对该共享状态信息的访问发生在不同的线程中,因此必须由互斥锁对其其进行保护。
await方法:必须在获取锁之后的调用,表示释放当前锁,阻塞当前线程;等待其他线程调用锁的signal或signalAll方法,线程唤醒重新获取锁。
Lock配合Condition,可以实现synchronized 与 对象(wait,notify)同样的效果,来进行线程间基于共享变量的通信。但优势在于同一个锁可以由多个条件队列,当某个条件满足时,只需要唤醒对应的条件队列即可,避免无效的竞争。
// 此类实现类似阻塞队列(ArrayBlockingQueue)
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
–count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
BlockingQueue#p#副标题#e##p#分页标题#e#
BlockingQueue阻塞队列实际上是一个生产者/消费者模型,当队列长度大于指定的最大值,生产线程就会被阻塞;反之当队列元素为空时,消费线程就会被阻塞;同时当消费成功时,就会唤醒阻塞的生产者线程;生产成功就会唤醒消费者线程;
内部使用就是ReentrantLock + Condition来实现的,可以参照上面的示例。