该系列依旧是参照android api27源码来学习。
关于锁的种类概念,可以参考 Java中的锁分类
Lock接口
该接口位于java.util.concurrent.locks
包下。
相对synchronized而言,Lock接口提供了更多可扩展的锁操作,有更灵活的结构,完全不同的属性,以及支持多个相关联的Condition对象。
锁是控制多线程访问共享变量的工具。通常锁提供对共享变量的独占访问,即同一时间只有一个线程可以获得锁,并且想要访问共享变量就要先得到锁。然而,有些锁允许并发访问共享变量,比如ReadWriteLock中的读锁。
synchronized语法是块结构,可以自动完成锁的释放;如果synchronized有嵌套使用,则锁的释放顺序和获取顺序是相反的。
而Lock必须手动控制锁的释放,但可以灵活控制释放顺序。语法结构如下:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
//确保锁一定会被释放
l.unlock();
}
Lock相对synchronized还提供了以下功能:
- 提供了非阻塞尝试获取锁的方法
tryLock()
。 - 提供了尝试获取可被中断的锁的方法
lockInterruptibly()
。 - 提供了获取具有时效的锁的方法
tryLock(long, TimeUnit)
。
Lock还可以提供其他的行为,比如保证顺序、非重入、死锁检测等。
Lock对象也可以作为synchronized语法中被锁住的对象,这和调用Lock对象自身的lock()方法没有任何关系。不过为了避免混淆,不推荐这样使用,除非是在Lock实现类的内部。
注:除非特别说明,否则传入null作为任何的参数将导致NullPointerException。
该接口的方法如下:
void lock()
阻塞方法,获得一个锁。如果锁暂时不可用,那么当前线程会休眠(被阻塞,进入WAITING状态),直到获取锁。
void lockInterruptibly() throws InterruptedException
阻塞方法,获取锁,除非当前线程被中断。若当前锁不可用,则线程阻塞,直到以下任意条件成立:
- 当前线程获得锁。
- 当前线程被中断,并且支持锁的获取过程被中断。
如果当前线程满足以下任意条件,则抛出InterruptedException,并且清除当前线程的中断状态:
- 在调用该方法之前,已经处于中断状态。
- 在获取锁的时候被中断,并且支持在获取锁的时候被中断。
boolean tryLock()
非阻塞方法。若锁可用,则获取锁,返回true;否则返回false。通常用法如下:
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
boolean tryLock(long time, TimeUnit unit) throws InterruptedException
如果锁在等待时间内可用,并且当前线程未被中断,则获取锁。
锁可用,则立即返回true。否则,线程阻塞,知道以下任意条件触发:
- 当前线程获得锁.
- 当前线程被中断,并且支持锁的获取过程被中断。
- 等待超时。
若当前线程满足如下任意条件,则抛出InterruptedException,并且清除线程的中断状态:
- 在调用该方法之前,已经处于中断状态。
- 在获取锁的时候被中断,并且支持在获取锁的时候被中断。
若等待时间不大于0,则不会等待。
void unlock()
释放锁。
Condition newCondition()
返回一个绑定到该Lock实例的Condition实例。
当前线程在调用Condition的等待方法之前,需要先获取锁。调用Condition的await()方法会释放锁,并在等待结束时重新获取锁。
Condition接口
Condition从Lock中获取,且每个Lock可以有多个Condition,彼此互不影响。await、signal方法等同于Object的wait、notify方法。Lock可以代替synchronizedde的使用,Condition可以代替Object的wait/notify方法的使用。
Condition需要与Lock绑定使用,通过Lock.newCondition方法获取。
举个例子。假设有一个支持put和take阻塞方法的有界buffer,所实现的生产者/消费者模型如下:
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();
}
}
}
ArrayBlockingQueue类正是这样的实现。
void await() throws InterruptedException
阻塞当前线程,直到被唤醒或者中断。与Condition相关联的Lock会被释放。在该方法返回之前,当前线程必须重新获取与Condition关联的锁。
当前线程满足以下任意条件,则抛出InterruptedException,并且清除中断状态:
- 调用await之前,已处于中断状态。
- 阻塞过程中被中断,并且支持线程挂起时被中断。
void awaitUninterruptibly()
与await()方法相比,不受中断影响,也不影响中断状态,只能被唤醒。
long awaitNanos(long nanosTimeout) throws InterruptedException
与await()相比,添加了超时时间。该方法返回剩余等待时间,若超时,则返回值不大于0。通常用法如下:
boolean aMethod(long timeout, TimeUnit unit) {
long nanos = unit.toNanos(timeout);
lock.lock();
try {
while (!conditionBeingWaitedFor()) {
if (nanos <= 0L)
return false;
nanos = theCondition.awaitNanos(nanos);
}
// ...
} finally {
lock.unlock();
}
}
boolean await(long time, TimeUnit unit) throws InterruptedException
该方法在效果上等同于awaitNanos(unit.toNanos(time)) > 0
boolean awaitUntil(Date deadline) throws InterruptedException
与await()相比,添加了等待截止时间,返回值表示是否超过了截止时间。通常用法如下:
boolean aMethod(Date deadline) {
boolean stillWaiting = true;
lock.lock();
try {
while (!conditionBeingWaitedFor()) {
if (!stillWaiting)
return false;
stillWaiting = theCondition.awaitUntil(deadline);
}
// ...
} finally {
lock.unlock();
}
}
void signal()
唤醒一个等待线程,效果与Object.notify()类似。
void signalAll()
唤醒所有等待线程,效果与Object.notifyAll()类似。
ReadWriteLock
ReadWriteLock维护一对相关联的Lock,一个针对读操作,一个针对写操作。读锁可以被多个线程同时持有,只要没有其他线程写入。而写锁是独享的。
ReadWriteLock的实现类必须保证读写操作的内存同步,也即成功获得读锁后,将可以看到上次释放写锁后的所有更新。
读写锁比互斥锁有更大的吞吐量。在只有一个线程写数据的时候,大多数情况下可以有任意数量的线程并发读取数据,读写锁正是利用了这一点。当然这是针对多核处理器而言。
读写锁相对于互斥锁是否提升性能,依赖于对数据读和写的频率、持续时间,以及数据争用情况,即同一时间访问数据的线程数。
虽然读写锁的操作简单直接,但实现类必须做出一些权衡,这会影响到性能:
- 在读操作和写操作都在等待的时候,恰逢一个写操作释放了写锁,这时是否授予读锁或者写锁?写操作拥有高优先级,因为读写锁本身适用于读多写少的情况,所以写操作占用时间少,频率低;读操作拥有低优先级,因为读可能需要很久,导致写操作进入漫长的等待;当然,公平起见,也可以按照请求的顺序来实现。
- 在一个读操作正在执行和一个写操作正在等待的情况下,是否授予读锁给其他的读操作。读操作的性能可能无限期延后写操作,而写操作会降低并发执行的能力。
- 锁是否可以重入?一个拥有写锁的线程可以再次请求写锁吗?或者可以同时获取读写锁吗?读锁可重入吗?
- 在不允许介于中间的写操作时,写锁可以降级成读锁吗?拥有读锁的读操作,可以优先于其他正在等待的读或写操作,将读锁升级为写锁吗?
Lock readLock()
获取读锁。
Lock writeLock()
获取写锁。
LockSupport
不多介绍了,参考java并发包系列---LockSupport
推荐阅读