为了解决原子性问题,Java加入了锁机制,同时保证可见性和有序性。JDK1.5的并发包中增加了Lock接口和相关的实现类来实现lock功能,比synchronized更加灵活。开发者可以根据实际场景选择相应的实现类。本文重点讲解其不同派生类的使用场景及其内部AQS的原理。并发问题的介绍和synchronized相关知识请阅读上一篇文章了解Java锁机制。锁定功能可以重入。synchronized和ReentrantLock都是可重入锁。可重入性表明锁分配机制是基于线程分配,而不是方法调用分配。举个简单的例子,当一个线程已经获取了锁,后面再获取同一个锁的时候,直接获取成功。但是获取锁和释放锁必须成对出现。响应中断当一个线程因为获取锁进入阻塞状态时,可以从外部中断该线程。调用者可以通过捕获InterruptedException来捕获中断。可以设置获取锁的超时时间。可以指定超时时间,根据返回值判断是否成功。获取锁公平性提供了两种选择:公平锁和非公平锁(默认)。公平锁,线程会按照请求的顺序获取锁,不允许跳队;非公平锁,允许跳队:当一个线程请求获取锁时,如果锁可用,则该线程将跳转队列中等待的线程并获取锁。考虑这样一种情况:A线程持有锁,B线程申请锁,于是B线程被挂起;当A线程释放锁时,B线程会被唤醒,于是再次尝试获取锁;同时,C线程也请求获取这把锁,那么C线程很可能在B线程被完全唤醒之前获取、使用、释放锁。这是一个双赢的局面,B获取锁的时刻(B被唤醒后才能获取锁)没有延迟,C更早获取锁,吞吐量也得到了提升。大多数情况下,非公平锁的性能要高于公平锁。另外这个公平是针对线程的,不能靠它来实现业务公平。它应该由开发人员自己控制,例如通过FIFO队列确保发布。读写锁允许读锁和写锁分离。读锁和写锁是互斥的,但是多个读锁可以并存,适用于读频率远大于写频率的场景。丰富的API提供了多种获取锁相关信息的方法。isFair():判断锁是否公平isLocked():判断锁是否被任何线程获取是否有线程在等待锁当前线程持有锁getQueueLength():获取等待锁的线程数所有特性使用起来都比较简单{lock.lock();//blockuntilconditionholdstry{//...methodbody}finally{//必须释放锁,unlock和lock成对出现。lock.unlock()}}}ReentrantReadWriteLock读写锁的实现具有上面列出的所有特性。并且写锁可以降级为读锁,反之亦然。classCachedData{Objectdata;volatilebooleancacheValid;finalReentrantReadWriteLockrwl=newReentrantReadWriteLock();voidprocessCachedData(){rwl.readLock().lock();if(!cacheValid){//必须先释放readlockbeforeacquiringwritelockrwl.readLock().unlock();Lock.write)lock();try{//Recheckstatebecauseanotherthreadmighthave//acquiredwritelockandchangedstatebeforewedid.if(!cacheValid){data=...cacheValid=true;}//Downgradebyacquiringreadlockbeforereleasingwritelockrwl.readLock().lock();}最后{rwl.writeLock()。unlock();//Unlockwrite,stillholdread}}try{use(data);}finally{rwl.readLock().unlock();}}}StampedLockStampedLock也是一个读写锁,提供两种读模式:optimisticread和悲观的阅读。乐观读允许在读过程中获得写锁后进行写操作!这样一来,我们读取到的数据可能会不一致,所以在读取过程中需要额外加一点代码来判断是否有写入。乐观锁就是乐观的估计在读的过程中有很大的概率不会有写,所以叫做乐观锁。反之,悲观锁就是写在读过程中被拒绝,即写必须等待。显然,乐观锁的并发效率更高,但是一旦出现小概率写和读数据不一致的情况,就需要检测再读。publicclassPoint{privatefinalStampedLockstampedLock=newStampedLock();privatedoublex;privatedoubley;publicvoidmove(doubledeltaX,doubledeltaY){longstamp=stampedLock.writeLock();//获取写锁try{x+=deltaX;y+=deltaY;}finally{stampedLock.unlockWrite(stamp);//释放写锁}}publicdoubledistanceFromOrigin(){longstamp=stampedLock.tryOptimisticRead();//获取乐观读锁//注意下面两行代码不是原子操作//假设x,y=(100,200)doublecurrentX=x;//这里已经读到x=100,但是x,y可能会被写线程修改为(300,400)doublecurrentY=y;//这里已经读到y,如果没有写,thereadiscorrectof(100,200)//如果有write,则read错误(100,400)if(!stampedLock.validate(stamp)){//检查乐观读锁后是否还有其他写锁stamp=stampedLock.readLock();//获得悲观读锁try{currentX=x;currentY=y;}finally{stampedLock.unlockRead(stamp);//释放悲观读锁}}returnMath.sqrt(currentX*currentX+currentY*currentY);}}ConditionCondition成为条件队列或条件变量,为一个线程提供一种暂停执行(等待)直到另一个线程通知某个状态条件现在可能为真的方法。由于对这种共享状态信息的访问发生在不同的线程中,因此它必须由互斥锁保护。await方法:必须在获取到锁后调用,表示释放当前锁,阻塞当前线程;等待其他线程调用锁的signal或signalAll方法,线程唤醒重新获取锁。Lock和Condition可以达到和synchronized和objects(wait,notify)一样的效果,实现线程间基于共享变量的通信。但是好处是同一个锁可以有多个条件队列。当满足某个条件时,只需要唤醒对应的条件队列即可,避免无效竞争。//这种实现类似于阻塞队列(ArrayBlockingQueue)classBoundedBuffer{finalLocklock=newReentrantLock();finalConditionnotFull=lock.newCondition();finalConditionnotEmpty=lock.newCondition();finalObject[]items=newObject[100];intputptr,takeptr,count;publicvoidput(Objectx)throwsInterruptedException{lock.lock();try{while(count==items.length)notFull.await();items[putptr]=x;if(++putptr==items.length)putptr=0;++count;notEmpty.signal();}finally{lock.unlock();}}publicObjecttake()throwsInterruptedException{lock.lock();try{while(count==0)notEmpty。await();Objectx=items[takeptr];if(++takeptr==items.length)takeptr=0;--count;notFull.signal();returnx;}finally{lock.unlock();}}}BlockingQueueBlockingQueue实际上是阻塞队列是一种生产者/消费者模型。当队列长度大于指定的最大值时,生产线程将被阻塞;否则,当队列元素为空时,消费者线程将被阻塞;同时,当消费成功时,阻塞线程会被唤醒生产者线程;如果生产成功,消费者线程将被唤醒;内部使用是通过ReentrantLock+Condition实现的,可以参考上面的例子。CountDownLatch称为倒数计时器锁。它初始化指定的值,调用countDown可以将值减一。当该值减为0时,会唤醒所有被调用await方法阻塞的线程。可以达到一组线程等待另一组线程完成任务的效果。classDriver{//...voidmain()throwsInterruptedException{CountDownLatchstartSignal=newCountDownLatch(1);CountDownLatchdoneSignal=newCountDownLatch(N);for(inti=0;i
