Lock 和 Condition
Lock 接口
在Lock接口出现之前,Java程序是靠synchronized关键字来实现锁功能的,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁,但是提供了多种synchronized关键字所不具备的同步特性。
Lock接口提供的synchronized关键字不具备的主要特性:
- 尝试非阻塞地获取锁
- 获取到的锁的线程能够响应中断
- 在指定的截止时间之前获取锁
一个生动的例子
public class LockExample {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
lock.lock();
try {
} finally {
lock.unlock();
}
}
}
ReentrantLock
重入锁ReentranLock,就是支持重进入的锁,表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁时的公平和非公平性选择。
不支持重入的锁,当自己获取锁之后,再获取锁的时候,该线程就会被自己阻塞,synchronized关键字也支持重进入。
公平性与否是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO。
一个生动的例子
public class FairAndUnfairTest {
private static ReentrantLock2 fairLock = new ReentrantLock2(true);
private static ReentrantLock2 unfairLock = new ReentrantLock2(false);
@Test
public void unfairTest() {
testLock(unfairLock);
}
@Test
public void fairTest() {
testLock(fairLock);
}
private void testLock(ReentrantLock2 lock) {
for (int i = 0; i < 5; i++) {
Job job = new Job(lock);
job.setName(String.valueOf(i));
job.start();
}
}
private static class Job extends Thread {
private ReentrantLock2 lock;
public Job(ReentrantLock2 lock) {
this.lock = lock;
}
@Override
public void run() {
lock.lock();
try {
System.out.println("Lock By[" + Thread.currentThread() + "], Waiting by " + lock.getQueuedThreads());
System.out.println("Lock By[" + Thread.currentThread() + "], Waiting by " + lock.getQueuedThreads());
} finally {
lock.unlock();
}
}
@Override
public String toString() {
return super.getName();
}
}
private static class ReentrantLock2 extends ReentrantLock {
private static final long serialVersionUID = 1L;
public ReentrantLock2(boolean fair) {
super(fair);
}
public Collection<Thread> getQueuedThreads() {
List<Thread> arrayList = new ArrayList<>(super.getQueuedThreads());
Collections.reverse(arrayList);
return arrayList;
}
}
}
输出的结果可能是:
公平锁 | 非公平锁 |
---|---|
Lock By[0], Waiting by [] | Lock By[0], Waiting by [] |
Lock By[0], Waiting by [1, 2] | Lock By[0], Waiting by [1, 2] |
Lock By[1], Waiting by [2, 4] | Lock By[1], Waiting by [2] |
Lock By[1], Waiting by [2, 4, 3] | Lock By[1], Waiting by [2] |
Lock By[2], Waiting by [4, 3] | Lock By[3], Waiting by [2] |
Lock By[2], Waiting by [4, 3] | Lock By[3], Waiting by [2] |
Lock By[4], Waiting by [3] | Lock By[4], Waiting by [2] |
Lock By[4], Waiting by [3] | Lock By[4], Waiting by [2] |
Lock By[3], Waiting by [] | Lock By[2], Waiting by [] |
Lock By[3], Waiting by [] | Lock By[2], Waiting by [] |
每条线程输出两条信息,从结果可以看出,公平性锁每次都是从同步队列中的第一个节点获取锁,而非公平锁不一定。
ReentrantReadWriteLock
排他锁就是在同一个时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和写线程均被阻塞。
读写锁维护了一个读锁和一个写锁,支持公平性选择、重进入、锁降级(遵循获取写锁、获取读锁再释放写锁的次序,写锁可以降级为读锁)。
一个生动的例子
public class ReentrantReadWriteLockExample {
private static Map<String, Object> map = new HashMap<String, Object>();
private static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private static Lock r = rwl.readLock();
private static Lock w = rwl.writeLock();
public static final Object get(String key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
public static final Object put(String key, Object value) {
w.lock();
try {
return map.put(key, value);
} finally {
w.unlock();
}
}
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
}
}
用一个非线程安全的HashMap作为缓存的实现,同时使用读写锁的读锁和写锁来保证HashMap是线程安全的。
Condition
任意一个Java对象,都有一组监视器方法,主要包括wait()、wait(long)、notify()以及notifyAll()方法,这些方法与synchronized关键字配合,可以实现等待/通知模式。
Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。它支持的特性如下:
- 多个等待队列
- 在等待状态中不响应中断
- 等待到将来的某个时间。
一个生动的例子
public class ConditionExample {
private Object[] items;
private int addIndex, removeIndex, count;
private Lock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();
public ConditionExample(int size) {
items = new Object[size];
}
public void add(Object t) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await();
}
items[addIndex] = t;
if (++addIndex == items.length) {
addIndex = 0;
}
count++;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object remove() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
Object result = items[removeIndex];
if (++removeIndex == items.length) {
removeIndex = 0;
}
count--;
notFull.signal();
return result;
} finally {
lock.unlock();
}
}
}
参考
- Java并发编程的艺术[书籍]