在java平台出现很长一段时间内,开发多thread程序只能使用java平台提供的synchronized和volatile关键字,以及object类中的wait,notify和notifyall方法。以上抽象层次比较低,在开发中使用起来比较繁琐,并且容易产生错误。并且线程间交互方式存在某些固定的模式,好比生产者-消费者模式和读-写模式。后来J2SE5.0引入juc包,提供了高层次的API,能够知足平常开发中常见的需求。
java
高级同步机制
数据结构
虽然非阻塞方式性能要优于阻塞方式,但并不是全部场景都要采用非阻塞的方式实现,有不少状况下仍然须要使用基于锁机制的阻塞方式实现。juc包的locks接口就是一个锁,能够经过其中的lock方法获取锁,unlock来解锁。使用lock接口的代码须要保证锁老是被释放,通常把unlock方法放在finally代码块中。lock方法获取锁的方式相似于synchronized关键词,以阻塞方式获取锁。另外还能够经过trylock方法以非阻塞方式获取锁。若是在调用trylock方法时没法获取锁,直接返回false,不会阻塞当前thread。利用trylock方法另一种重载形式能够指定超时时间。若是指定了超时时间,当没法获取锁时,当前thread会阻塞,但等待的时间不会超进指定的超时时间,同时thread也是能够被中断的。
性能
另一个与锁相关的接口是ReadWriteLock.rwl接口实际上表示的是两个锁,一个是读取操做相关的锁,另外一个是写入操做使用的排他锁。该接口适合于解决常见的读取-写入问题相似场景。在没有thread进行写入操做时,进行读取操做的多个thread均可以获取读取锁,而进行写入操做的thread只有在获取写入锁后才能进行写入操做。多个thread能够同时进行读取操做,可是同一时刻只容许一个thread进行写入操做。在大多数状况下,对一个数据结构的读取操做次数要远多于写入操做的次数。
ui
juc。locks包中提供lock接口和readwritelock接口的基本实现,分别为reentrantlock和reentrantreadwritelock类。这两具类共同特征是可重入性,即容许一个thread屡次获取同一个锁。reentranlock类对象能够有一个全部者thread,表示上一次成功获取该锁,但尚未释放锁的thread。reentrantlock类的对象同时保存了全部者thread在该对象上加锁的次数。经过getholdcount方法能够获取当前的加锁次数。若是reentrantlock类的对象当前没有全部者thread,则当前thread获取锁的操做会成功,加锁次数为1。在随后的操做中,thread能够再次获取该锁,这也是可重入的含义所在。每次加锁操做会使加锁次数加1,而每一次调用unlock方法释放锁会使加锁次数减1。当加锁次数变为0,该锁会被释放,能够被其余thread获取。
线程
在建立reentrantlock类对象时能够经过一个额外的boolean类型参数来声明使用更加公平的加锁机制。在使用锁机制会遇到一个问题是thread饥饿问题。当多个thread同时竞争某个锁时,可能有的thread一直没法成功获取锁,一直处于没法运行状态。thread饥饿是有些程序应该避免的问题。若是在建立reentrantlock类的对象时添加了额外的参数true,则reentrantlock会使用相对公平的锁分配策略。当锁处于可被获取状态时,在因为尝试获取该锁而处于等待状态的thread中,等待时间最长的thread会成功获取这个锁。这就避免了thread饥饿问题。不带参数的trylock方法会忽略公平模式的设置。
code
可重入锁优点在于减小了锁在各thread间传递次数,能够提升程序的吞吐量。为了提升程序总体吞吐量应该尽量使用可重入锁。lock接口代替synchronized,相对应的condition接口替代object类的wait,notify和notifyall方法。使用condition接口时也须要与一个对应的lock接口实现对象关联起来。经过lock接口的newcondition方法能够建立新的condition接口的实现对象。在调用condition接口的方法以前,也须要使用lock接口的方法来获取锁。condition接口提供了多个相似object类的wait方法的方法,最基本的是await方法,调用该方法会使当前thread进行等待状态,直到被唤醒或被中断。另一种await方法的重载形式能够指定超时时间。方法awaitnanos以纳秒数为单位指定超时时间,该方法返回值是剩余等待时间的估计值。相似的awaituntil方法也能够指定超时时间,只不过指定的不是要通过的时间而是超时发生的时间点,参数是一个java.util.date类的对象。前面几种方法都会响应其余thread发出的中断请求,而awaituninterruptibly方法则不会处理中断请求。
对象
与condition接口中等待方法相对应的是signal和signalall方法,至关于object的notify和notifyall方法,示例
继承
Lock lock = new ReentrantLock();接口
Condition condition = lock.newCondition();队列
lock.lock();
try{
while(logic condition){
condition.await();
}
}finally{
lock.unlock();
}
底层同步器
在一个multi thread中,thread 间可能存在多种不一样的同步方式,一种比较常邮的需求是对有限个共享资源的同步访问,multi thread程序中不少场景均可以抽象成这类同步方式。好比对某个监视器对象的互斥访问,其实是多个thread在竞争惟一的一个资源。若是系统中安装了两台打印机,那么须要进行打印操做的多个thread想互竞争这两个资源。当多个thread在等待同一个资源时,从公平的角度出发,这些thread会被放入到一个先入先出FIFO队列中,当资源变成可用时,处于队首的thread会获取该资源。
若是程序中同步方式能够抽象成对有限个资源的同步访问,可使用juc。locks包中的abstractqueuesynchronizer类和abstractqueuedlongsynchronizer类做为实现的基础。这两个类的做用是相同的,只不过前者在内部使用一个int类型的变量来维护内部状态,然后者使用一个long类型的变量。能够将这个内部变量理解成共享资源的个数。经过getstate,setstate和compareandset这三个方法来更新这个内部变量的值。它们都是abstract的,所以须要继承并重写其中包含的部分方法后才以使用。一般作法是把它们的子类做为一个java内部类。外部的java类提供具体的同步方式。
public class SimpleResourceManager{ private final InnerSynchronizer synchronizer; private static class InnerSynchronizer extends AbstractQueuedSynchronizer{ InnerSynchronizer(int numOfResources){ setState(numOfResources); } protected int tryAcquireShared(int qcquires){ for(;;){ int available = getState(); int remaining = available - acquires; if(remaining < 0 || compareAndSetState(available,remaining)){ return remaining; } } } protected boolean tryReleaseShared(int releases){ for(;;){ int available = getState(); int next = available + releases; if(compareAndSetState(available,next)){ return true; } } } // } public SimpleResourceManager(int numOfResources){ synchronizer = new InnerSynchronizer(numOfResources); } public void acquire() throws InterruptedException { synchronizer.qcquireSharedInterruptibly(1); } public void release(){ synchronizer.releaseShared(1); } }