tutorials sitehtml
Locks (and other more advanced synchronization mechanisms) are created using synchronized blocks, so it is not like we can get totally rid of the synchronized keyword.
锁的实现是利用synchonized, wait(),notify()方法实现的。因此不能够认为锁能够彻底脱离synchonized实现。java
Java包 JUC java.util.concurrent.locks 包括了不少lock接口的实现了类,这些类足够使用。
可是须要知道如何使用它们,以及这些类背后的理论。JUC包教程安全
用synchonized:能够保证在同一时间只有一个线程能够执行 return ++count
:多线程
public class Counter{ private int count = 0; public int inc(){ synchronized(this){ return ++count; } } }
如下的Counter类用Lock代替synchronized 达到一样的目的:
lock() 方法会对 Lock 实例对象进行加锁,所以全部其余对该对象调用 lock() 方法的线程都会被阻塞,直到该 Lock 对象的 unlock() 方法被调用。函数
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类是怎么设计的?this
一个Lock类的简单实现:线程
javapublic class Lock{ private boolean isLocked = false; public synchronized void lock() throws InterruptedException{ while(isLocked){ wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); } }
while(isLocked)
循环, 又被称为spin lock
自旋锁。当 isLocked
为 true
时,调用 lock()
的线程在 wait()
调用上阻塞等待。为防止该线程没有收到 notify()
调用也从 wait()
中返回(也称做虚假唤醒),这个线程会从新去检查 isLocked 条件以决定当前是否能够安全地继续执行仍是须要从新保持等待,而不是认为线程被唤醒了就能够安全地继续执行了。若是 isLocked 为 false,当前线程会退出 while(isLocked) 循环,并将 isLocked 设回 true,让其它正在调用 lock() 方法的线程可以在 Lock 实例上加锁。设计
当线程完成了临界区(位于 lock() 和 unlock() 之间)中的代码,就会调用 unlock()。执行 unlock() 会从新将 isLocked 设置为 false,而且通知(唤醒)其中一个(如有的话)在 lock() 方法中调用了 wait() 函数而处于等待状态的线程。code
synchronized 同步块是可重入的
。这意味着: 若是一个java线程进入了代码中的同步块synchonzied block,并所以得到了该同步块使用的同步对象
对应的管程monitor object上的锁那么这个线程能够进入由同一个管程对象所同步的另外一个 java 代码块
htm
前面的Lock的设计就不是可重入的:
javapublic class Reentrant2{ Lock lock = new Lock(); public outer(){ lock.lock(); inner(); lock.unlock(); } public synchronized inner(){ lock.lock(); //do something lock.unlock(); } }
一个线程是否被容许退出 lock() 方法是由 while 循环(自旋锁)中的条件决定的。当前的判断条件是只有当 isLocked 为 false 时 lock 操做才被容许,而没有考虑是哪一个线程锁住了它。
因此须要对Lock的设计作出以下修改,才能可重入。
javapublic 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() 的线程就能够退出该方法(译者注:“被容许退出该方法” 在当前语义下就是指不会调用 wait() 而致使阻塞)。
除此以外,咱们须要记录同一个线程重复对一个锁对象加锁的次数。不然,一次 unblock() 调用就会解除整个锁,即便当前锁已经被加锁过屡次。在 unlock() 调用没有达到对应 lock() 调用的次数以前,咱们不但愿锁被解除。
如今这个 Lock 类就是可重入的了。
Starvation and Fairness 饥饿和公平
一个线程由于其余线程长期占有CPU而本身得到不到,这种状态称为Starvation
. 解决线程饥饿的方法是公平机制fairness公平机制
,让全部线程都能公平的有机会去得到CPU。
阻塞状态
的线程无限期被阻塞Java's synchronized code blocks can be another cause of starvation.
等待状态
的对象无限期等待这里细说一下:多线程经过共享一个object对象,来调用对象的wait/notifyAll 来致使线程等待或者唤醒; 每次一个线程进入同步块,其余全部线程陷入等待状态;而后active线程调用notifyALL()函数唤醒全部等待线程,全部线程竞争,只有一个线程竞争成功,得到CPU执行。竞争失败的线程处于就绪状态,长期竞争失败的线程就会饥饿。
线程之间的对资源(object)竞争致使的饥饿,为了不竞争,因此想办法一次唤醒一个线程。也就是下面讲的FairLock 公平锁机制。
使用锁lock来代替同步块synchonized block
每个调用
lock()
的线程都会进入一个队列,当解锁后,只有队列里的第一个线程 (队首)
被容许锁住Fairlock
实例,全部其它的线程都将处于等待状态,直到他们处于队列头部。
公平锁实现机制:为每个线程建立一个专属锁对象(而非多个线程共享一个对象,来wait/notify()),而后用一个队列来管理这些锁对象,尝试加锁的线程会在各自的对象上等待,当一个线程unlock的时候,只通知队列头的锁对象,以唤醒其对应的线程
为了让这个 Lock 类具备可重入性,咱们须要对它作一点小的改动:
javapublic class FairLock { private boolean isLocked = false; private Thread lockingThread = null; private List<QueueObject> waitingThreads = new ArrayList<QueueObject>(); public void lock() throws InterruptedException{ QueueObject queueObject = new QueueObject(); boolean isLockedForThisThread = true; synchronized(this){ waitingThreads.add(queueObject); } while(isLockedForThisThread){ synchronized(this){ isLockedForThisThread = isLocked || waitingThreads.get(0) != queueObject; if(!isLockedForThisThread){ isLocked = true; waitingThreads.remove(queueObject); lockingThread = Thread.currentThread(); return; } } try{ queueObject.doWait(); }catch(InterruptedException e){ synchronized(this) { waitingThreads.remove(queueObject); } throw e; } } } public synchronized void unlock(){ if(this.lockingThread != Thread.currentThread()){ throw new IllegalMonitorStateException( "Calling thread has not locked this lock"); } isLocked = false; lockingThread = null; if(waitingThreads.size() > 0){ waitingThreads.get(0).doNotify(); } } }