Java并发编程实践-电子书-07章.pdf
《Java并发编程实践-电子书-07章.pdf》由会员分享,可在线阅读,更多相关《Java并发编程实践-电子书-07章.pdf(22页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、第七章第七章 显示锁显示锁 第七章 显示锁.1 7.1.Lock和ReentrantLock.2 7.2.对性能的考察.4 7.3 Lock与Condition.8 7.4.在内部锁和重入锁之间进行选择.13 7.5.读-写锁.14 参考文献.21 Linux公社(LinuxIDC.com)是包括Ubuntu,Fedora,SUSE技术,最新IT资讯等Linux专业类网站。相对于以前的版本,Java 5.0 引入了新的调节共享对象访问的机制,即重入锁(ReentrantLock)。重入锁可以在内部锁被证明受到局限时,提供可选择的高级特性。它具有与内在锁相同的内存语义、相同的锁定,但在争用条件下
2、却有更好的性能。同时提供了读写锁,与互斥锁相比,读取数据远大于修改数据的频率时能提升性能。在第 3 章讲解 JDK 并发 API 时已经介绍过 ReentrantLock,本章做一些提升和补充。7.1.Lock 和和 ReentrantLock Lock 接口定义了一组抽象的锁定操作。与内部锁定(intrinsic locking)不同,Lock 提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显式的。这提供了更加灵活的加锁机制,弥补了内部锁在功能上的一些局限不能中断那些正在等待获取锁的线程,并且在请求锁失败的情况下,必须无限等待不能中断那些正在等待获取锁的线程,
3、并且在请求锁失败的情况下,必须无限等待。Lock 接口主要定义了下面的一些方法:1)void lock():获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态。2)void lockInterruptibly()throws InterruptedException:如果当前线程未被中断,则获取锁。如果锁可用,则获取锁,并立即返回。如果当前线程在获 取 锁 时 被 中 断,并 且 支 持 对 锁 获 取 的 中 断,则 将 抛 出 InterruptedException,并清除当前线程的已中断状态。3)boolean tryLock():如果锁
4、可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。4)boolean tryLock(long time,TimeUnit unit)throws InterruptedException:如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。5)void unlock():释放锁。6)Condition newCondition():返回绑定到此 Lock 实例的新 Condition 实Linux公社(LinuxIDC.com)是包括Ubuntu,Fedora,SUSE技术,最新IT资讯等Linux专业类网站。例。调用 Condition.aw
5、ait()将在等待前以原子方式释放锁,并在等待返回前重新获取锁。ReentrantLock实现了Lock接口。获得ReentrantLock的锁与进入synchronized块具有相同的语义,释放 ReentrantLock 锁与退出 synchronized 块有相同的语义。相比于 synchronized,ReentrantLock 提供了更多的灵活性来处理不可用的锁。下面具体来介绍一下 ReentrantLock 的使用。1.实现可轮询的锁请求实现可轮询的锁请求 在内部锁中,死锁是致命的唯一的恢复方法是重新启动程序,唯一的预防方法是在构建程序时不要出错。而可轮询的锁获取模式具有更完善的错
6、误恢复机制,可以规避死锁的发生。如果你不能获得所有需要的锁,那么使用可轮询的获取方式使你能够重新拿到控制权,它会释放你已经获得的这些锁,然后再重新尝试。可轮询的锁获取模式,由 tryLock()方法实现。此方法仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。此方法的典型使用语句如下:Lock lock=.;if(lock.tryLock()try /manipulate protected state finally lock.unlock();else /perform alternative actions 2
7、.实现可定时的锁请求实现可定时的锁请求 当使用内部锁时,一旦开始请求,锁就不能停止了,所以内部锁给实现具有时限的活动带来了风险。为了解决这一问题,可以使用定时锁。当具有时限的活Linux公社(LinuxIDC.com)是包括Ubuntu,Fedora,SUSE技术,最新IT资讯等Linux专业类网站。动调用了阻塞方法,定时锁能够在时间预算内设定相应的超时。如果活动在期待的时间内没能获得结果,定时锁能使程序提前返回。可定时的锁获取模式,由tryLock(long,TimeUnit)方法实现。3.实现可中断的锁获取请求实现可中断的锁获取请求 可中断的锁获取操作允许在可取消的活动中使用。lockIn
8、terruptibly()方法能够使你获得锁的时候响应中断。7.2.对性能的考察对性能的考察 当 ReentrantLock 被加入到 Java 5.0 中时,它提供的性能要远远优于内部锁。如果有越多的资源花费在锁的管理和调度上,那用留给应用程序的就会越少。更好的实现锁的方法会使用更少的系统调用,发生更少的上下文切换,在共享的内存总线上发起更少的内存同步通信。耗时的操作会占用本应用于程序的资源。Java 6 中使用了经过改善的管理内部锁的算法,类似于 ReentrantLock 使用的算法,从而大大弥补了可伸缩性的不足。因此 ReentrantLock 与内部锁之间的性能差异,会随着 CPU、
9、处理器数量、高速缓存大小、JVM 等因素的发展而改变。下面具体的构造一个测试程序来具体考察 ReentrantLock 的性能。构造一个计数器 Counter,启动 N 个线程对计数器进行递增操作。显然,这个递增操作需要同步以防止数据冲突和线程干扰,为保证原子性,采用 3 种锁来实现同步,然后查看结果。测试环境是双核酷睿处理器,内存 3G,JDK6。第一种是内在锁,第二种是不公平的 ReentrantLock 锁,第三种是公平的ReentrantLock 锁。首先定义一个计数器接口。package locks;public interface Counter public long getVa
10、lue();public void increment();Linux公社(LinuxIDC.com)是包括Ubuntu,Fedora,SUSE技术,最新IT资讯等Linux专业类网站。下面是使用内在锁的计数器类:package lockbenchmark;public class SynchronizedCounter implements Counter private long count=0;public long getValue()return count;public synchronized void increment()count+;下面是使用不公平 ReentrantLo
11、ck 锁的计数器。package lockbenchmark;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class ReentrantUnfairCounterLockCounter implements Counter private volatile long count=0;private Lock lock;public ReentrantUnfairCounterLockCounter()/使用非公平锁,true就是公平锁 lock=new
12、 ReentrantLock(false);public long getValue()return count;public void increment()lock.lock();try count+;finally lock.unlock();下面是使用公平的 ReentrantLock 锁的计数器。package lockbenchmark;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class ReentrantFairLockCounter
13、 implements Counter Linux公社(LinuxIDC.com)是包括Ubuntu,Fedora,SUSE技术,最新IT资讯等Linux专业类网站。private volatile long count=0;private Lock lock;public ReentrantFairLockCounter()/true就是公平锁 lock=new ReentrantLock(true);public long getValue()return count;public void increment()lock.lock();try count+;finally lock.un
14、lock();下面是总测试程序。package lockbenchmark;import java.util.concurrent.CyclicBarrier;public class BenchmarkTest private Counter counter;/为了统一启动线程,这样好计算多线程并发运行的时间 private CyclicBarrier barrier;private int threadNum;/线程数 private int loopNum;/每个线程的循环次数 private String testName;public BenchmarkTest(Counter co
15、unter,int threadNum,int loopNum,String testName)this.counter=counter;barrier=new CyclicBarrier(threadNum+1);/关卡计数=线程数+1 this.threadNum=threadNum;this.loopNum=loopNum;this.testName=testName;public static void main(String args)throws Exception int threadNum=2000;int loopNum=1000;new BenchmarkTest(new
16、SynchronizedCounter(),threadNum,loopNum,内部锁).test();Linux公社(LinuxIDC.com)是包括Ubuntu,Fedora,SUSE技术,最新IT资讯等Linux专业类网站。new BenchmarkTest(new ReentrantUnfairCounterLockCounter(),threadNum,loopNum,不公平重入锁).test();new BenchmarkTest(new ReentrantFairLockCounter(),threadNum,loopNum,公平重入锁).test();public void t
17、est()throws Exception try for(int i=0;i threadNum;i+)new TestThread(counter,loopNum).start();long start=System.currentTimeMillis();barrier.await();/等待所有任务线程创建,然后通过关卡,统一执行所有线程 barrier.await();/等待所有任务计算完成 long end=System.currentTimeMillis();System.out.println(this.testName+count value:+counter.getValu
18、e();System.out.println(this.testName+花费时间:+(end-start)+毫秒);catch(Exception e)throw new RuntimeException(e);class TestThread extends Thread int loopNum=100;private Counter counter;public TestThread(final Counter counter,int loopNum)this.counter=counter;this.loopNum=loopNum;public void run()try barrie
19、r.await();/等待所有的线程开始 for(int i=0;i this.loopNum;i+)counter.increment();barrier.await();/等待所有的线程完成 catch(Exception e)throw new RuntimeException(e);Linux公社(LinuxIDC.com)是包括Ubuntu,Fedora,SUSE技术,最新IT资讯等Linux专业类网站。对三种锁分别设置两个不同的参数:不同线程数和每个线程数的循环次数。最后记录每种锁的运行时间(单位:ms),形成下表。循环次数 1000 循环次数1000 线 程 数200 线 程 数
20、500 线程数1000 线程数2000 内在锁 62 313 406 875 非公平锁 31 94 250 859 公平锁 4641 17610 44671 57391 循环次数 200 循环次数200 线 程 数200 线 程 数500 线程数1000 线程数2000 内在锁 47 94 109 265 非公平锁 16 32 125 906 公平锁 781 3031 8671 13625 分析统计结果,在线程数小于 2000 的情况下,非公平可重入锁的性能要优于内部锁。公平可重入锁的性能最差。同时发现内部锁其实也是一个非公平锁。7.3 Lock 与与 Condition 当使用 synchr
21、onized 进行同步时,可以在同步代码块中使用 wait 和 notify 等方法。在使用显示锁的时候,通过将 Condition 对象与任意 Lock 实现组合使用,为每个对象提供多个等待方法。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。条件(Condition,也称为条件队列或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true、另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保Linux公社(LinuxIDC.com)是包括
22、Ubuntu,Fedora,SUSE技术,最新IT资讯等Linux专业类网站。护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition()方法。下面使用可重入锁与 Condition 替代 synchronized 实现生产者-消费者模式。生产者-消费者问题一般是,有一个缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一
23、个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。可以在单独的等待集合中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。下面是缓冲区类 LockedBuffer,在这个类的 put 和 take 方法中使用了可重入锁与条件变量:package conditionlock;import java.util.concurrent.locks.Condition;import java.util.concurrent.l
24、ocks.Lock;import java.util.concurrent.locks.ReentrantLock;public class LockedBuffer /可重入锁 final Lock lock=new ReentrantLock();/两个条件对象 final Condition notFull=lock.newCondition();final Condition notEmpty=lock.newCondition();/缓冲区 final Object items=new Object10;int putptr,takeptr,count;/计数器 /放数据操作,生产者
25、调用该方法 public void put(Object x)throws InterruptedException lock.lock();try /如果缓冲区满了,则线程等待 while(count=items.length)notFull.await();itemsputptr=x;if(+putptr=items.length)putptr=0;+count;/向消费者线程发送通知 Linux公社(LinuxIDC.com)是包括Ubuntu,Fedora,SUSE技术,最新IT资讯等Linux专业类网站。notEmpty.signal();finally lock.unlock();
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 并发 编程 实践 电子书 07
限制150内