简单锁的使用示例
[java]
view plain
copy
- lock.lock();
- ….. ///do something
- lock.unlock();
- ….
通过lock.lock() 进行资源竞争,竞争失败的进程被阻塞在lock()调用上,成功获得锁的进程将进入临界区,并在退出临界区时释放锁,然后其他进程再次进行竞争,并使得一个进程可以进入临界区。
如下是锁的一个简单demo
[java]
view plain
copy
- public class UnFairLock {
- private volatile boolean isLocked = false;
- private Thread lockedForThread = null;
- public synchronized void lock() throws InterruptedException{
- while(isLocked){
- wait();
- }
- lockedForThread = Thread.currentThread();
- isLocked = true;
- }
- public synchronized void unlock(){
- if(lockedForThread != Thread.currentThread()){
- throw new IllegalMonitorStateException(“Current thread does’t hold the lock”);
- }
- isLocked = false;
- lockedForThread = null;
- this.notifyAll();
- }
- }
该锁由一个boolean变量的标志符控制当前是否已经加锁,volatile修饰时必须的,使得每次读取标志符都直接从内存中获取,而不会因为缓存导致无法读取到最新变化。在lock中,循环等待也是必须的,尽管wait()方法会一直等待下去,没有唤醒不会自己活过来,但是,不能保证唤醒它的就是它需要等待的条件得到了满足。
另外一个变量LockedForThread,记录当前获得锁的进程。因为锁在解锁的时候需要判断解锁进程是否是获得锁的进程(java doc中有说明)。
但是这个锁在如下程序中会出现死锁,因为它不支持重入。
[java]
view plain
copy
- package cn.yuanye.concurrence.lock;
- public class CriticalObject{
- private UnFairLock lock = new UnFairLock();
- public void f1() throws InterruptedException{
- lock.lock();
- System.out.println(“get lock in f1(),try to invoke f2()”);
- f2();
- lock.unlock();
- }
- public void f2() throws InterruptedException{
- lock.lock();
- System.out.println(“get lock in f2()”);
- lock.unlock();
- }
- public static void main(String[] args) throws InterruptedException{
- CriticalObject obj = new CriticalObject();
- obj.f1();
- }
- }
此段程序会输入“get lock in f1(),try to invoke f2()”之后便停滞不前了,也就是阻塞在了f2()的调用上了。
重入是指,一个线程可以多次获得它已经获得的锁。在上例中,直接在主线程中调用了f1(),主线程获取到了锁权限,当调用f2()时,如果支持重入,那么它也应该能够重新获取到该锁的权限,而不是卡死在第二次加锁上。
如下是一个简单的支持重入的锁,与不支持重入锁相比,多了一个nlock变量,用于记录被加锁的次数。
[java]
view plain
copy
- package cn.yuanye.concurrence.lock;
- public class ReentrancyLock {
- private volatile boolean isLocked = false;
- private int nlock = 0; //locked times
- private Thread lockedForThread = null;
- public synchronized void lock() throws InterruptedException{
- if(lockedForThread == Thread.currentThread()){ //invoke by the thread which owns the lock
- nlock ++ ;
- return;
- }
- while(isLocked){
- wait();
- }
- isLocked = true;
- nlock++;
- lockedForThread = Thread.currentThread();
- }
- public synchronized void unlock(){
- if(lockedForThread != Thread.currentThread()){
- throw new IllegalMonitorStateException(
- “Current thread does’t hold the lock”);
- }
- nlock –;
- if(nlock == 0){
- isLocked = false;
- lockedForThread = null;
- notifyAll();
- }
- }
- }
但是上面的锁机制都不是公平的。所谓公平,就是先提出锁请求的,先得到锁。但是上述的锁,却无法决定下一次应该由谁获得锁。notifyAll()会唤醒所有等待该锁的进程,notify() 会随机唤醒一个进程,所以都是不能满足要求的。
如下是一个公平锁的实现
[java]
view plain
copy
- package cn.yuanye.concurrence.lock;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.TimeUnit;
- class LockObject{
- private volatile boolean isNotified = false;
- /**
- * wait until the {@value isNotified} is true
- * @throws InterruptedException
- * */
- public synchronized void doWait() throws InterruptedException{
- while(!isNotified){
- wait();
- }
- isNotified = false;
- }
- /**
- * notify thread blocked in the doWait
- * */
- public synchronized void doNotify(){
- isNotified = true;
- notify();
- }
- @Override
- public boolean equals(Object o){
- return (o == this);
- }
- }
- public class FairLock {
- private volatile boolean isLocked = false;
- private Thread lockedThread = null;
- private List<LockObject> locks = new LinkedList<LockObject>();
- public void lock() throws InterruptedException {
- LockObject lock = new LockObject();
- boolean isAvaliable = false;
- synchronized (this) {
- locks.add(lock);
- }
- while (!isAvaliable) {
- synchronized (this) {
- isAvaliable = !isLocked && locks.get(0) == lock;
- if (isAvaliable) {
- isLocked = true;
- locks.remove(0);
- lockedThread = Thread.currentThread();
- return;
- }
- }
- try {
- lock.doWait();
- } catch (InterruptedException e) {
- synchronized (this) {
- locks.remove(lock);
- }
- throw e;
- }
- }
- }
- public synchronized void unlock(){
- if(Thread.currentThread() != lockedThread){
- throw new IllegalMonitorStateException(
- “Calling thread has not locked this lock”);
- }
- lockedThread = null;
- isLocked = false;
- if(locks.size() > 0){
- locks.get(0).doNotify();
- }
- }
- }
要实现公平锁,就需要记录下各个线程申请所得顺序,在释放锁的时候根据该顺序进行通知。上例通过LockObject与各个申请锁的线程对应,并将这些锁对象顺的存入List,在释放锁的时候,顺序冲List获取对象,通知该对象对应的线程。
与UnFailLock和ReentrancyLock对比,FairLock的lock()没有synchronized修饰,而是在内部分两步进行了同步。
第一步,将对应的锁对象放入List末尾。
第二部,判断是否能够获取锁对象。判断依据是当前锁没有加锁并且该线程对应的锁对象在List的头部。
是否能加两步直接省去,而直接将lock()修饰为synchronized呢?不能!
在UnFailLock和ReentrancyLock中,可以这么做是因为wait方法会释放锁。而在FairLock中,lock.doWait() 是在锁对象上调用的wait方法,而不是在FairLock对象上,所以该方法不会释放在FairLock上的锁,注意lock.doWait() 是在同步块之外的。