ReentantLock
参考资料:
1 java 并发实践
2 JDK 文档以及源代码
Reentantlock在jdk文档中的介绍如下:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
其完成了java synchorized的语义.
此类的构造方法接受一个可选的公平 参数。当设置为 true
时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。不过要注意的是,公平锁不能保证线程调度的公平性。因此,使用公平锁的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁时。还要注意的是,未定时的 tryLock方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁是可用的,此方法就可以获得成功。
上述事例中,涉及到经常使用的ReentrantLock的方法, lock()和unlock().
其外, ReentantLock 提供了Condition的相关操作.
Condition
将 Object
监视器方法( wait,notify和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock
实现组合使用,为每个对象提供多个等待 set(wait-set)
Condition Exsample [from JDK api --- Condition]
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();
}
}
}
示例代码 2
针对上面的例子, 如下说明:
1 在调用lock.await()的时候,必须获取lock的锁定(而不是简单的synchronized(notEmpty)). 否则会抛出异常:
java.lang.IllegalMonitorStateException 注意了, 当使用Object中的wait()和signal()或者signalAll()时,一定要确保已经获取了监视对象的锁定. 如下代码就是错的:
synchronized(someObject){
await();// 错误,这里的await需要锁定this 而不是object可以改为someObject.wait()
//signal()同上.
...
}
2 condiionObject.await()必须出现在while(condition)中, 原因有:
2.1 防止虚假唤醒;
2.2 解释代码为【示例代码2】中的while改为if, get方法的notFull.signal()改为notFull.signalAll()
如果有N个线程在notFull这个Condition对象的wait-set中等待【put操作中】, 只有一个活跃线程调用这个condition对象的notifyAll方法, 即是在take操作中的notFull.signal()误写为notFull.signalAll(); 那么,N个线程会同步,依次执行
items[putptr] = x;
假设这个时候putptr已经是MAX_SIZE -2, 线程数N > 2; 那么会抛出相关的越界异常;
上述的的解释可能会比较“不现实”. 但是, while语句的作用就是强制N个等待线程在某种原因唤醒的时候, 依然能够检查等待条件.
3 signal的操作会等待锁的释放,一般写的程序的最后是一种编程习惯.
4 await()和sleep区别, await()将会让线程处于休眠状态释放掉锁. 而sleep将"持锁而睡".
使用conditionObject.await(long time,TimeUnit unit);
api : 此方法会等待相关的conditionObject.signal()或者conditionObject.signalAll()方法, signal或者signalAll在给定的时间time内完成,那么就返回true, 否则返回false. 注意,此方法并不是去获取锁本身,而是"等待通知".当然的,当通知出发此方法的返回时, 当前被唤醒的对象是一定会去获取lock的.
如下代码:[保证ReentrantLock和condition是同一个对象]
public void run() {
lock.lock();
try{
if("thread_1".equals(threadName)){
System.out.println("thread_1 到达...");
//测试代码就不写入while()中,通常情况避免虚假唤醒是要将await至于while(condition)
//中的.
boolean result = condition.await(4, TimeUnit.SECONDS);
if(result){
System.out.println("true");
}else{
System.out.println("false");
}
}else{
System.out.println("thread_2 到达...");
//Thread.sleep(500);
condition.signal();
}
}catch(Exception e){
e.printStackTrace();
}finally{
System.out.println(threadName + "释放锁");
lock.unlock();
}
}
当应用程序在运行期间等待一个执行时间不太确定的操作时候,用此方法是很合适的. 可以用一个变量来改变下次等待的时间,如下伪代码:
.....
while(condition){
try{
boolean waitTime =conditionObject.await(wantedTime,...);
if(waitTime == false) {
//超时
changeWantedTime(...);
}
}
}
使用乐观的tryLock(): 当不成功时候,会迅速返回false而不是进入等待区等待.
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
<to be continue...>