即便大部分同步装置的功能和使用方式不一样(锁, semaphores, 阻塞队列等),但它们的内部实现仍是至关一致的.换句话说它们都有相同的组成部分.了解这些组成部分有助于咱们设计一个同步装置.下文会一个个分析这些组成部分.html
大部分同步装置的目的是保证临界区代码在多线程环境下的安全访问.要作到这点,一个同步装置一般须要如下几个部分:java
并非全部的同步装置都由这些部分组成,可能会有例外.但大部分同步装置都是由它们中的一到多个部分组成的.算法
同步装置中的状态用因而否容许一个线程取得访问权限的检查条件.在Lock中,状态用来记录一个布尔值,用于表示Lock是否已经被锁住.在BounedSemaphore中,内部状态用来记录一个整型计数器,用于表示已经发送的信号数量以及可以发送的最大上限.阻塞队列中,状态用于记录一个保存有相关队项的列表,用于表示队列中进队的队项,以及可以容纳的最大队项数量.安全
如下给出Lock和BoundedSemaphore的状态实现的片断代码.多线程
public class Lock{
//state is kept here
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
...
}
复制代码
public class BoundedSemaphore {
//state is kept here
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public synchronized void take() throws InterruptedException{
while(this.signals == bound) wait();
this.signal++;
this.notify();
}
...
}
复制代码
访问条件用于决定一个线程在执行检查和设置状态的方法时,是否容许设置状态.同步装置中,访问条件是状态的典型应用.而访问条件的典型应用是解析成true或false,以在一个while循环中防止线程意外唤醒风险的发生.post
在Lock中,访问条件简化为检查isLocked成员变量的值.在BoundedSemaphore中,一共有两个访问条件决定了take()和release()方法的调用结果.当一个线程调用take()方法时须要检查signals变量是否已经达到上限.当一个线程调用release()方法时须要一样须要检查signals变量.this
如下给出Lock和BoundedSemaphore的访问条件实现的片断代码.咱们须要注意访问条件一直在while循环中检查.spa
public class Lock{
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
//access condition
while(isLocked){
wait();
}
isLocked = true;
}
...
}
复制代码
public class BoundedSemaphore {
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public synchronized void take() throws InterruptedException{
//access condition
while(this.signals == bound) wait();
this.signals++;
this.notify();
}
public synchronized void release() throws InterruptedException{
//access condition
while(this.signals == 0) wait();
this.signals--;
this.notify();
}
}
复制代码
一旦线程得到访问临界区代码的权限,它须要更改同步装置内部状态来阻塞其余线程进入临界区.换句话说,状态须要反映一个线程当前正在执行临界区代码.这将对其余线程检查访问条件取得访问权限产生影响.线程
在Lock中,状态更改表现为代码isLocked = true
.在Semaphore中变现为代码signals--
或是signals++
.设计
如下给出Lock和BoundedSemaphore的更改状态实现的片断代码.
public class Lock{
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
while(isLocked){
wait();
}
//state change
isLocked = true;
}
public synchronized void unlock(){
//state change
isLocked = false;
notify();
}
}
复制代码
public class BoundedSemaphore {
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public synchronized void take() throws InterruptedException{
while(this.signals == bound) wait();
//state change
this.signals++;
this.notify();
}
public synchronized void release() throws InterruptedException{
while(this.signals == 0) wait();
//state change
this.signals--;
this.notify();
}
}
复制代码
一旦线程更改了同步装置的状态,那么它须要通知到正在等待进入临界区的其余线程.由于此次状态更改可能会将其余线程的访问条件置换为true.
通知策略一共有如下三种类型:
通知全部等待线程比较简单.只要在线程调用wait()方法的对象上调用notify()便可.调用notify()并不能保证多个等待线程中哪个被通知到.所以称为"随机通知".
有时候你须要指定通知一个特定的等待线程而不是随机通知.好比你须要按照线程调用同步装置的顺序来通知线程或是按照优先级来通知线程.那么咱们须要存储每个线程与调用wait()方法的对象之间的关系.当须要通知特定的等待线程时,只须要调用线程调用wait()方法的对象的notify()方法便可.这样的例子在公平与饥饿一文中有说起.
如下给出了随机通知一个线程的片断代码:
public class Lock{
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
while(isLocked){
//wait strategy - related to notification strategy
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify(); //notification strategy
}
}
复制代码
同步装置一般包含有两种类型的方法,其中检查和设置是第一种(设置是第二种).检查和设置是指线程调用检查方法来比较访问条件和同步装置内的状态,若是条件符合预期线程则设置同步装置内的状态来反映当前线程已经取得访问临界区的权限.
一般经过置换状态来使访问条件解析为false,以阻塞其余线程取得访问权限.但不老是如此,例如在读写锁中,一个线程经过更改读写锁的内部状态来反映该线程已经取得读取权限,但在没有线程请求进行写操做的状况下,其余线程仍然能够取得读取权限.
检查和设置操做必须是原子,即不容许其余线程在检查和设置状态的期间进行干扰.
下面给出了检查和设置方法在程序中的关键步骤:
若有必要能够在检查前设置状态
比较访问条件和状态
若是访问条件不知足预期则进入阻塞状态
若是访问条件知足预期则设置状态,若有必要同时通知其余等待线程
在以前Java中的读写锁文章说起的ReadWriteLock类中的lockWrite()方法就是一个检查和设置的实例方法.线程在调用lockWrite()时,在检查状态前先设置(writeRequests++).而后再经过canGrantWriteAccess()方法来对比内部状态和访问条件.若是符合预期则在退出调用方法前设置状态.咱们注意到这个方法并无通知其余等待线程.
public class ReadWriteLock{
private Map<Thread, Integer> readingThreads = new HashMap();
private int writeAccesses = 0;
private int writeRequests = 0;
private Thread writingThread;
public synchronized void lockWrite() throws InterruptedException{
writeRequests++;
Thread callingThread = Thread.currentThread();
while(! canGrantWriteAccess(callingThread)){
wait();
}
writeRequests--;
writeAccesses++;
writingThread = callingThread;
}
}
复制代码
在BoundedSemaphoer对象中包含了两个检查和设置方法: take()和release().两个方法都会检查和设置内部状态.
public class BoundedSemaphore {
private int signals = 0;
private int bound = 0;
public BoundedSemaphore(int upperBound){
this.bound = upperBound;
}
public synchronized void take() throws InterruptedException{
while(this.signals == bound) wait();
this.signals++;
this.notify();
}
public synchronized void release() throws InterruptedException{
while(this.signals == 0) wait();
this.signals--;
this.notify();
}
}
复制代码
设置方法是同步装置中常见的第二种类型的方法.设置方法仅仅是设置同步装置中的内部状态而没有去检查它.一个典型的设置方法实例是Lock对象中的unlock()方法.一般一个线程在释放已持有的锁时不须要检查锁是否已经释放过了.
下面给出了设置方法在程序中的关键步骤:
下面给出unlock方法示例:
public class Lock{
private boolean isLocked = false;
public synchronized void unlock(){
isLocked = false;
notify();
}
}
复制代码
该系列博文为笔者复习基础所著译文或理解后的产物,复习原文来自Jakob Jenkov所著Java Concurrency and Multithreading Tutorial