我们以一个java的同步块代码开始
public class Counter{ private int count = 0; public int inc(){ synchronized(this){ return ++count; } } }
public class Counter{ private Lock lock = new Lock(); private int count = 0; public int inc(){ lock.lock(); int newCount = ++count; lock.unlock(); return newCount; } }
下边这是一个简单的lock的实现
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(); } }
java中的同步块是再入的。也就是说,如果一个java线程进入了一个同步代码块,在监控对象上拿到了同步锁并进入同步状态,线程可以其他的java代码块,在相同的监视对象上进行同步。下边这是一个例子
public class Reentrant{ public synchronized outer(){ inner(); } public synchronized inner(){ //do something } }
早前看到的实现并不是重入,如果我们像下边这样重写Reentrant类,线程调用outer会在inner方法中的lock.lock中阻塞。
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方法中线程再一次的想锁住锁实例,这会失败(意思是线程将会被阻塞),因为锁实例已经在outer方法中被锁住了。线程将会被第二次阻塞的原因是它调用了lock方法而没有调用unlock犯法,我们看看这个lock方法的实现之后就很明显了
public class Lock{ boolean isLocked = false; public synchronized void lock() throws InterruptedException{ while(isLocked){ wait(); } isLocked = true; } ... }
注意while循环现在把锁实例也考虑进去了,或者锁被解锁调用线程是锁实例。在while循环中的条件决定是否线程要退出,当前情况下isLocked必须是false才可以,而不管线程是否已经锁住它了。为了让锁类重入,我们需要做一些小小的改变
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循环现在把锁实例也考虑进去了,或者锁被解锁调用线程是锁实例
翻译的不好,还有一些没有翻译出来,想看原文的同学可以看下边的链接
原文地址:http://tutorials.jenkov.com/java-concurrency/locks.html