java concurrency <Lock>
Java中的锁是一种线程的同步机制像是synchronized blocks(同步块),除此之外锁要比同步快更高级, 使用同步块创建锁(另一种更高级的同步机制),因此我们不能完全摆脱synchronized关键字。
从Java 5开始,java.util.concurrent.locks包中包含几个锁实现,因此您可能不必实现自己的锁。 但是,我么可以稍稍的研究一下他的原理。
一个简单的锁
public class Counter{
private int count = 0;
public int inc(){
synchronized(this){
return ++count;
}
}
}
注意inc()方法中的synchronized(this)块。 此块确保只有一个线程可以一次执行返回++计数。 同步块中的代码可以有更牛逼的实现,但是简单的++计数用同步快可以满足预期。
他的Counter类可能是这样写的,而是使用Lock而不是同步块:
public class Counter{
private Lock lock = new Lock();
private int count = 0;
public int inc(){
lock.lock();
int newCount = ++count;
lock.unlock();
return newCount;
}
}
下面是锁的实现:
public class Lock{
private boolean isLocked = false;
public synchronized void lock()
throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
}
重申一下前面文章中说的内容
- 在调用对象上的wait()方法时,必须要在同步快中调用
- wait()调用之后,是等待其他的线程调用notify()或者是notifyAll()
- 之所以要加上while(){……}和成员变量是为了防止信号量的丢失
锁重入
Java中的同步块是可重入的。 这意味着,如果Java线程进入同步的代码块,从而在监视对象上锁定该块,则该线程可以进入同一监视对象上同步的其他Java代码块。 下面是一个例子:
public class Reentrant{
public synchronized outer(){
inner();
}
public synchronized inner(){
//do something
}
}
注意outer()和inner()如何被声明为synchronized,在Java中等同于一个synchronized(这个)块。 如果一个线程调用outer(),那么从outer()内部调用inner()没有问题,因为这两个方法(或块)都在同一个监视器对象(“this”)上同步。 如果一个线程已经拥有监视对象上的锁定,它可以访问同一个监视器对象上同步的所有块。 这叫做reentrance。 线程可以重新输入它已经保持锁定的任何代码块。
怎样理解这句话呢?打个比方,一栋楼是一个对象,楼有一个大门,里边又有很多小门,每一扇门都有一个一把锁。如果有人一旦持有了这栋楼的大门的🔑,进入之后他可以随意进入其他的小门。
之前的重入会阻塞,例如:
public class Reentrant2{
Lock lock = new Lock();
public outer(){
lock.lock();
inner();
lock.unlock();
}
public synchronized inner(){
lock.lock();
//do something
lock.unlock();
}
}
调用outer()的线程将首先锁定Lock实例。 那么它将调用inner()。 在inner()方法中,线程将再次尝试锁定Lock实例。 这将失败(意味着线程将被阻止),因为Lock实例已被锁定在outer()方法中。
在第二次调用lock()时,线程将被阻塞的原因在于它们之间没有其他线程调用unlock()),当我们看到lock()实现时,这是很明显的:
修改一下之前的Lock
public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock()
throws InterruptedException{
Thread callingThread = Thread.currentThread();
while(isLocked && lockedBy != callingThread){
wait();
}
isLocked = true;
lockedCount++;
lockedBy = callingThread;
}
public synchronized void unlock(){
if(Thread.curentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
}
请注意,while循环(旋转锁)现在也将锁定Lock实例的线程考虑在内。 如果锁定解锁(isLocked = false)或调用线程是锁定Lock实例的线程,则while循环将不会执行,并且线程调用lock()将被允许退出该方法。
另外,我们需要计算锁被同一个线程锁定的次数。 否则,单次调用unlock()将解锁锁定,即使锁已被多次锁定。 在锁定锁定线程之前,我们不希望解锁锁,它已经执行了与lock()调用相同数量的unlock()调用。
Lock类现在可以重入。
在finally块中调用unlock
当使用Lock来保护关键部分时,关键部分可能会抛出异常,重要的是从finally子句中调用unlock()方法。 这样做确保锁定解锁,因此其他线程可以锁定它。 这是一个例子:
lock.lock();
try{
//do critical section code, which may throw exception
} finally {
lock.unlock();
}
这个小结构确保锁定解锁,以防在临界区域的代码中抛出异常。 如果在finally子句中没有调用unlock(),并且从关键部分抛出异常,则Lock将永远保持锁定,从而导致该Lock实例上调用lock()的所有线程都不会停止。
网友评论