Java Concurrency in Practice学习笔记

Java Concurrency in Practice阅读

阅读时间:2017/5/17-2017/5/24
第一部分 基础知识 1
第2章 线程安全性 1
第3章 对象的共享 2
第4章 对象的组合 3
第5章 基础构建模块(重点章节) 4
第二部分 结构化并发应用程序 8
第6章 线程安全性 8
第7章 任务取消 9
第8章 线程池的使用 9
第9章 图形用户界面应用程序 10
第三部分 活跃性、性能与测试 11
第10章 避免活跃性危险 11
第11章 性能和可伸缩性 11
第12章 并发程序的测试 13
第四部分 高级主题 14
第13章 显示锁 14
第14章 构建自定义的同步工具 16
第15章 原子阻塞与非阻塞同步机制 16
第16章 Java内存模型 18
总结 20
附录代码 22
代码1(容易忽略同步造成Exc)附录: 22
代码2(阻塞队列以及闭锁)附录: 23
代码3(闭锁CounotDownLatch)附录: 26
代码4(FutureTask简单实例)附录: 28
代码5(Semaphore信号量)附录: 29
代码6(Semaphore信号量)附录: 31
代码7(ExecutorService.invokeAll()的使用)附录: 32
代码8(FutureTask.get(time,TimeUnit)超时示例)附录: 34
代码9(ThreadLocal获取数据库连接Conn)附录: 37
代码10(锁顺序死锁)附录: 38
代码11(锁顺序死锁)附录: 40

第一部分 基础知识
第一部分为线程的基础知识,也即为一些入门知识,本书在基础知识部分之前(第一章:简介)详细介绍了线程的历史,以及为何我们需要并发编程,主要是提高多核处理器的资源利用率、加快程序并行运算速度,提高复杂程序运行的性能,并发编程的拥有如此的优点,但同时并发编程较为复杂,难度较高,且容易出错,且出错的概率不定,很难查找出错的地方,这就需要基本功扎实的开发者去设计强健的代码,将程序运行的安全性得到保障。总之,由于并发编程巨大的优势和并发编程所存在易出错、风险性等等问题使之成为一个十分抢眼且必须掌握的技术栈。另外,笔记去掉第1章简介内容,此段即为简介的说明和补充。
第2章 线程安全性
即程序对于共享变量能够十分正常执行并得到我们期望的结果,对于共享变量不会随着线程的操作而出现前后不一致的结果,就是线程安全性。个人理解,此外,面向对象中的抽象和封装会降低程序的性能,我们应该遵循的正确的编程方法是:首先使代码运行正确,然后再提高代码的速度。EJ中建议是谨慎地进行优化,因为绝大多数优化是错的,除非开发者是大师级程序员,否则不要轻易进行代码优化。这一部分概念很多,粗略上记录一下这些概念,理解线程之前,自己先理解三个概念
原子性:通俗意义上来讲,即为要么都发生成功,要么就都不发生,比如x=4;为原子性,x++,y=x,x=x+1;都不是原子性的
可见性:即为多个线程同时访问一个共享变量的时候,其中一个线程修改了这个值,那么其他线程能立刻看到这个修改后的值。如用volatile这个关键字修饰的变量为可见变量。但int volatile a=3;a++;a++同样不是原子性的
有序性:即程序按照程序代码的顺序依次执行。synchronized关键字和ReentrantLock(第四部分高级主题的部分,建议是尽量使用synchronized)可以同时保证原子性和有序性,当然也是可见的。volatile可以保证部分的有序性
这三个性质将贯穿整本书,可以说所有的并发均为这些特性的运用和深入,(自己的感想)
竞态条件:多个线程访问同一资源时,如果对资源的访问顺序敏感,就称为静态条件。
数据竞争:如果线程访问共享的非final类型的域而没有采用同步时,就会出现数据竞争。如public int getData(){return data;}
内置锁:同步代码块,就是我们经常用的synchronized关键字
显式锁:Lock接口,ReentrantLock这个显式锁类,自后面的高级主题会详细说到。具有更加丰富的功能(但性能和java6之后和synchronized差不了多少,建议少用显式锁,而多采用内置锁)。
锁最主要就是保护数据,并使之成为原子性。
活跃性与性能:创建新线程、线程阻塞、线程唤醒这些都是要消耗资源的,并且多线程竞争相同的资源会造成性能的严重下降,所以我们必须考虑活跃性的问题,主要方法是降低同步粒度,多采用同步代码块而不是同步方法方式,并尽可能缩小同步代码块的范围(但前提是确保共享数据的安全和原子性),同时也不能太细,否则会出现很多同步代码块。总之,合适为宜。
第3章 对象的共享
对象的共享来源于使对象能像在多个线程中同一时刻访问相同的数据,但其中会涉及到一个问题,那就是对象的安全性,如果一个对象不能被安全地发布,那么就很容易出现数据竞争这一类的情况,会造成访问对象的数据前后不一致。如对一个懒汉非线程安全的单例模式
class Demo{
static Object o;
public Object getInstance(){
o=new Object();
return o;}
}
出现的问题就是非安全的发布共享。失效数据也是由于没有同步而引起的,两者类似。所以如果要达到对对象安全的共享时,就是加锁,1是可以保证互斥行为,2是可以保证内存的可见性,3是操作的原子性。
另外:在单线程的情况下,JVM会对程序进行重排序(改变程序运行的顺序,但不影响最终的结果),重排序会使得程序更符合CPU运行的操作,使CPU运行执行程序的效率更高。
volatile关键字:当变量被声明为volatile类型后,就声明了这个变量是共享的,因此该变量上不会发生重排序,在读取volatile类型的变量时总会返回最新写入的值。并不建议过度依赖volatile变量提供的可见性,仅仅当volatile变量能简化代码的实现以及对同步策略的验证的时候,才应该使用他们。volatile变量正确的使用方式:1确保他们自身状态的可见性;2确保他们所引用对象的状态的可见性;3以及标识一些重要的程序生命周期事件的发生。
发布与逸出参见上面的单例模式,概念较为简单,在此不再详细阐述,只注意一点的是,final修饰的量不可更改,所以无论有没有同步方法,对final对象的访问将永远是线程安全的(内部的写数据除外)。不可变的对象一定是线程安全的,任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有使用同步。但如果final类型的域所指向的是可变对象,那么在访问这些域所指向的对象的状态时仍然需要同步。
ThreadLocal类:通常用于防止对可变的单实例变量或全局变量进行共享,降低代码的可重用性,并在类之间引入隐含的耦合性,在使用时要注意这点。
如何安全地共享对象?许多并发错误都是由于没有理解共享对象的一些“既定规则”而导致的。当发布一个对象时,必须明确地说明对象的访问方式:
1.线程封闭。线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只由这个线程修改。
2.只读对象。在没有额外同步的情况下,共享的只读对象可以由多个线程并发地访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。
3、线程安全共享。线程安全的对象在它内部实现同步,因此多个线程可以通过对象的公有接口而不是进一步的同步。
4.保护对象。被保护的对象只能通过持有特定的锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。
第4章 对象的组合
设计线程安全的类的过程中,需要包含以下三个基本要素:1是找出构成对象状态的所有变量,2是找出约束状态变量的不变性条件,3是建立对象状态的并发访问管理策略。
自行总结一点:尽量不要扩大加锁的范围,而应当对可变对象的那部分加锁(缩小加锁的粒度),且尽量不要对当前对象加锁,而应当使用同步代码块。
对象的组合即使内部的类是线程安全的,但是组合得到的对象不一定是,如AtomicInteget,应该特别注意这一点。
此外,我们应该优先选择重用一些现有的类而不是创建新的类。多用Java类库中“基础模块”中的类。不要自己造轮子。
最后,每次编写同步代码时,要写javadoc,详细说明是否是线程安全,是否有某些特定的锁影响其行为等等一些问题。
第5章 基础构建模块(重点章节)
Java平台类库包含了丰富的并发基础模块,早期的有Vector和HashTable(基于陈旧的Dictionary类,线程安全性能低已被弃用而选择HashMap),
另外,同步类会出现的问题,在一些复合操作下如迭代的情况下多线程并发地修改容器时,会出现错误。
如:
List list= Collections.synchronizedList(new ArrayList ());
for (Integer i:list){
dosomething()
}
当迭代的时候,如果出现某一线程对List表修改就出报错。抛出ConcurrentModificationException异常,必须在迭代的过程持有对象容器的锁。如果不希望加锁,另外一种替代的方法是“克隆容器”。
隐藏迭代器出错:代码见附录1。控制台输出多出报错,但如果将HashSet改变为Collections.synchronizedSet(set);便不会报错。出错的原因是因为在打印输出的时候我们必须先获得HiddenIterator的锁,但在实际调试中很容易忽视它。得到的教训是:如果状态与保护它的同步代码之间相隔越远,那么开发人员就越容易忘记在访问状态时使用正确的同步。
并发容器:并发容器是针对多个线程并发访问而设计的,ConcurrentHash Map、CopyOnWriteArrayList、PriorityQueue、BlockingQueue等等
·Java5.0增加ConcurrentHashMap(类),用来替代同步且基于散列的Map,采用分段锁机制来提高并发性能和伸缩性,ConcurrentHashMap不能被加锁来执行独断访问。只有当应用程序需要加锁Map以进行独占访问时,才应该放弃使用ConcurrentHashMap.
·CopyOnWriteArrayList(类),用于在遍历操作为主要操作的情况下代替同步的List,并且在迭代期间不需要对容器进行加锁或者复制。仅当迭代操作远远多于修改操作时,才应该使用“写入时复制”容器。
·Queue(接口),用来临时保存一组等待处理的元素。几种实现包括ConcurrentLinedQueue(传统的先进先出)队列,以及PriorityQueue(非并发的)优先队列。Queue上的操作不会阻塞,如果队列为空,那么获取元素的操作将返回空值。
·BlockingQueue(接口),它扩展了Queue,增加了可阻塞的插入和获取等操作。如果队列为空,那么获取元素的操作将一直阻塞,直到队列中出现一个可用的元素。如果队列已满,那么元素插入将会一直阻塞。在生产者和消费者模式中非常有用,
BlockingQueue有多种实现LinedBlockingQueue和ArrayBlockingQueue是FIFO队列,二者和LinkedList、ArrayList类似。PriorityBlockingQueue是一个按照优先级排序的队列,BlockingQueue的一个实例如代码附录2。其中用到了后面的同步工具类闭锁CountDownLatch
中断:
Thread.interrupt() 中断线程。返回值void
Thread.interrupted()测试当前线程是否为空测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false。
Thread.isInterrupted()测试线程是否已经中断。线程的中断状态 不受该方法的影响
同步工具类(重点):
根据其自身的状态来协调线程的控制流。如阻塞队列,信号量(Semaphore),栅栏(Barrier)以及闭锁(Latch)。
闭锁:
作用相当于一扇门,作为同步工具,在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能够通过,当到达结束状态时(countDown一直减到0了)就会打开并允许所有的线程通过。闭锁的测试程序如程序3。
FutureTask:
Future也可以用来闭锁,FutureTask实现了RunnableFuture接口(这个接口实现了Runnable接口和Future接口),FutureTask表示的计算是通过Callable来实现的,相当于一种可生成结果的Runnable,并且可以处于3种状态:等待运行,正在运行和运行完成,Future.get()的行为取决于任务的状态。如果完成,则立即返回结果,否则阻塞直到任务完成,返回结果或者抛出异常。如下列测试代码,见附录代码4
信号量(Semaphore)
计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。计数信号量还可以用来实现某种资源池,或者对容器施加边界。Semaphore中管理着一组虚拟的许可,许可的数量可通过构造函数来指定在执行操作时可以首先获得许可,并且杂使用后释放许可。代码示例如附录代码5
栅栏(Barrier)
栅栏闭锁类似。闭锁是一次性对象,一旦进去终止状态,就不能被重置。它能阻塞一组线程直到某个事件发生,栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行。。闭锁用于等待事件,而栅栏用于等待其他线程。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都达到栅栏位置。如果所有线程都到达了栅栏位置,那么栅栏位置将会打开,所有线程都被释放,而栅栏将被重置以便下次使用。代码示例如附录代码6所示。
第一部分总结
1.首先要对整个数学模型有一个清晰的了解,哪些是我们需要共享的数据,哪些我们需要设置为final、volatile、synchronized内置所是用在方法上还是同步代码块上,此外,对于一些并发工具包的工具类的使用要熟悉,闭锁,栅栏、信号量、FutureTask和Callabel的使用。写程序的时候,要注意是否是原子性的,可见性呢?是有序还是无序的,这些都要考虑,最后,要形成写javadoc的好习惯。(自行体会总结)
2.可变状态至关重要,所有的并发问题都可以归结于如何协调对并发状态的访问,可变状态越少,就越容易确保线程的安全性
3.尽量将域声明为final类型,除非需要他们是不可变的。
4.不可变对象一定是线程安全的,不可变对象能极大地降低并发编程的复杂性。它们更为简单而且安全,可以任意共享而无需使用加锁或保护性复制等机制
5.封装有助于管理的复杂性
6.用所来保护每个可变变量
7.当保护同一个不变形条件的的所有变量时,要使用同一个锁
8.在执行复合操作期间,要持有锁
9.如果从多个线程中访问同一个可变变量时没有同步机制,那么程序会出问题的。
10.不要自以为是的推断出不需要使用同步。
11.在设计过程中药考虑线程安全,或者再文档中明确地指出它不是线程安全的
12.将同步策略文档化javadoc。

第二部分 结构化并发应用程序
第6章 线程安全性
按照“任务执行”来设计应用程序结构时,第一步需要做的就是找出清晰的任务边界。服务器应用应该同时表现出良好的吞吐量和快速的响应性。但串行处理机制的程序无法提高吞吐率或快速响应性。此外不能无限制创建线程,一是因为线程创建的成本和生命周期的开销很高,二是会造成资源消耗,尤其是内存。三是在稳定性方面,可创建线程数量在不同的平台上而不同。
在一定范围内,增加线程可以提高系统的吞吐率,但如果超过这个范围,再创建线程只会降低程序的执行速度。
Executor框架
Executor框架基于生产者-消费者模式,提交任务相当于生产者,执行任务为消费者,其线程池如下:
newFixedThreadPool 固定线程,传入一个固定线程的数目
newCachedThreadPool 可缓存线程,如果规模超过处理需求,将回收空闲线程
newSingleThreadExecutor 单线程的Executor,相当于newFixedThreadPool线程数量为1
newScheduledThreadPool 创建一个固定长度的线程池,而且以延时或定时的方式来执行任务,类似于Timer
表2.1 Executor的线程池
Executor的生命周期:由于Executor以异步的方式来执行任务,因此在任何时刻,之前提交任务的状态不是立刻可见的。其生命周期有3中状态:运行,关闭和已终止。shutdown方法将执行平缓的关闭过程:不再接受新的任务,同时等待已经提交的任务执行完成–包括那些还未开始执行的任务,并且不再启动队列中尚未开始执行的任务。awaitTermination来等待Executor到达终止状态。
携带任务的Callable和Future
此处和第一部分的FutureTask类似,主要用来进行延迟计算的处理,另外,主要是注意一下ExecutorService.invokeAll()这个方法,可以简化代码,当把所有的future获得结果时,不必新建一个List.,代码示例如下代码附录7
第7章 任务取消
取消分为用户主动的取消、有时间限制的取消(Future.get(long,TimeUnit)),还有应用程序事件、错误和程序关闭的时候。
其中一种协作机制是设置某个“已请求取消”标志。通过volatile关键字来设计。
中断线程,interrupt()方法。在本文档第一部分有中断部分的一部分说明,但是在此补充一点,调用interrupt并不意味着立刻停止目标线程正在进行的工作,而只是传递了请求中断信息。对中断正确的理解为:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。通常,中断是实现取消最合理的方式。另外中断策略、相应中断等都是对中断的一种运用。重要的是思想。
此处,要着重注意的是通过Future来实现中断,也就是get()方法超时。传入两个参数,一个是数值,第二个是单位。详见附录代码8
关闭ExecutorService
两种关闭方法:1是shutdown正常关闭,2是使用shutdownNow强制关闭,强制关闭的速度更快,但风险也更大,因为任务很可能在执行到一半时被结束,建议不要使用这种关闭。
另外,还有毒丸对象,其含义是,“当得到这个毒丸对象立刻停止”可以使用毒丸来关闭线程。
此外,守护线程与主线程。在JVM启动时创建的所有线程,除主线程外,其他的线程都是守护线程。当创建一个新的线程时,新线程将继承创建它的线程的守护状态,可以在Thread中设置setDaemon(true),守护线程也叫后台线程,只要主线程结束了,后台线程自动会被杀死。
第8章 线程池的使用
ThreadLocal:
本地线程类,使得每一个线程都可以拥有某个变量的一个私有版本,只有当线程本地值的生命周期受限于任务周期时,在线程池的线程中使用ThreadLocal才有意义,而在线程池的线程中不应该使用ThreadLocal在任务之间传递值。其中一个数据库连接如下附录代码9
饥饿死锁:
在线程池中,如果任务依赖于其他任务,那么可能发生死锁,后面在第三部分活跃性危险中有详细说明。
设置线程池大小,一般而言,在计算密集型任务,在拥有Ncpu个处理器(比如说我的笔记本4核),当线程池的大小为Ncpu+1(对于我的本也就是5)通常可以实现最优的利用率。
ThreadPoolExecutor
它对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,本章中较难掌握的线程池。在此,没有深入地去探讨这个类。
第9章 图形用户界面应用程序
这一章主要运用第一部分和第二部分的一些知识还有并发工具包的内容来进行桌面应用程序的构建,如何在AWT和Swing中用,由于图形化界面不是我们研究的重点,在此,差不多快速看了一遍并没有深入去探讨如何在图形化编程中利用并发编程的优势构建出强壮、性能优越的程序。

第三部分 活跃性、性能与测试
第10章 避免活跃性危险
死锁:
书上一个很经典的案列,哲学家进餐问题,这是一个锁顺序死锁。对应的代码如代码附录10,此外,还有动态的锁顺序死锁,这是因为我们并不清楚地知道是否在锁顺序上有足够的控制权来避免死锁的发生,如将资金从一个账户转到另外一个账户,两个账户(先是对自己的账户,而后是他人的账户)依次加锁,但同时,另外一个账户也转账呢?这时就会出现死锁。解决的办法是统一锁的顺序。还有的时候,会出现在协作对象之间发生死锁。总而言之,出现死锁均是对受保护数据的同步范围扩大或加锁顺序不对,当我们确立加锁的顺序并划分好锁粒度时,便不会出现死锁这种情况(个人愚见)
此外,我们应该多使用开放调用这种模式,开放调用,如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用。依赖开放调用的类通常能表现出更好的行为。
最后,还有一个定时锁,也就是在超过时间范围内没有获得锁的话,就会返回一个失败信息。显示锁(第四部分高级主题将会详细说到)的tryLock(time,TimeUnit)就是如此。
另外,注意活锁的概念,该问题是尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败,说地直白一点,就是两个人遇到了一起,过于客气都让路,但又在另外一条路上阻塞了,于是两个人又同时让路,又阻塞了……如此周而复始。要解决这个问题,需要在重试机制中引入随机性。需要让他们等待一段随机的时间。
第11章 性能和可伸缩性
关于性能:程序的性能可以采用多个指标来衡量,例如延迟时间、吞吐率、可伸缩性等,其中一些指标例如服务时间、等待时间等用于衡量程序的运行速度,一些指标例如吞吐率用于衡量程序的处理能力也就是在一定资源的情况下,能完成多少工作;
Amdahl定律:如果使用线程主要是为了发挥多个处理器的处理能力,那么就必须对问题进行合理的并行分解,并使得程序能有效地使用这种潜在的并行能力。有两种降低锁粒度的技术:锁分解(将一个锁分解为两个锁),锁分段(把一个锁分解为多个锁)
线程引入的开销:
1是上下文切换,上下文切换需要一定的开销,而在线程调度过程中需要访问由操作系统和JVM共享的数据结构。减少上下文开销,主要是减少锁的持有时间。因为持有时间越短那么发生竞争的情况就越少,上下文切换的次数就少。反应在程序就是请求服务的时间不宜过长。还有就是减少锁竞争的来源。比如通过把I/O操作从处理请求的线程转移到一个专门的线程。
2是内存同步,在synchronized和volative提供的可见性保证中可能会使用一些特殊指令,即内存栅栏(Memory Barrier)。内存栅栏可以刷新缓存使缓存无效、刷新硬件的写缓冲、以及停止执行管道。在大多数的内存栅栏中,大多数操作都是不能被重排序的。无竞争的同步对应用程序整体性能影响微乎其微,有竞争的同步对性能带了来较大的影响;不要过度担心非竞争同步带来的开销,JVM已经进行了额外的优化以进一步降低或者消除开销,因此优化的重点放在有锁竞争的地方;
3是阻塞,当线程无法获取某个锁需要被挂起,这个过程包含两次额外的上下文切换,以及所有必要的操作系统操作和缓存操作:被阻塞的线程在其时间片还未用完之前就要被交换出去,而后当获取所请求的锁时又被切换回来(由于锁竞争而导致阻塞时,线程在持有锁时将存在一定的开销:当它释放锁时,必须要告诉操作系统恢复运行阻塞的线程)
减少锁的竞争:
可以通过三种方式减少锁的竞争:1.减少锁的持有时间、2.降低锁的请求频率、3.使用带协调机制的独占锁。
在并发程序中,对可伸缩性最主要的威胁就是独占方式的资源锁。
1缩小锁的范围(快进快出)
减少锁的持有时间,同步的代码块尽量小,不要包含大量的计算操作或者阻塞操作;
2减小锁的粒度
降低锁的请求频率;锁分解或者锁分段,采用多个相互独立的锁来保护独立的状态变量,从而改变这些状态变量有单个锁来保护的情况,可以减小锁的粒度,但是发送死锁的风险提高;
锁分解:如果一个锁需要保护多个相互独立的状态变量,可以将锁分解为多个锁,每个锁只保护一个变量,降低锁的请求频率;
锁分段:可以将锁进一步扩展为对一组独立对象上得锁进行分解,被称为所分段;例如ConcurrentHashMap的机制,锁分段一个缺点是,与采用单个锁实现独占访问相比,需要获得多个锁来实现独占访问将更加困难并且开销更高;
3避免热点域
在某些应用中,我们会使用一个共享变量缓存常用的计算结果。每次更新操作都需要修改该共享变量以保证其有效性。例如,队列的 size,counter,链表的头节点引用等。在多线程应用中,该共享变量需要用锁保护起来。这种在单线程应用中常用的优化方法会成为多线程应用中的“热点域 (hot field) ”,从而限制可伸缩性。如果一个队列被设计成为在多线程访问时保持高吞吐量,那么可以考虑在每个入队和出队操作时不更新队列 size 。 ConcurrentHashMap 中为了避免这个问题,在每个分片的数组中维护一个独立的计数器,使用分离的锁保护,而不是维护一个全局计数
4放弃独占锁
例如并发容器、读-写锁、不可变对象、原子变量等。ReadWriteLock:实现了一种在多个读取操作以及单个写入操作情况下得加锁机制:可以并发读,写操作独占锁;原子变量:提供了一种方式来降低更新“热点域”时的开销,原子变量类提供了在整数或者对象引用上得细粒度的原子操作,并且使用了现代处理器中提供的底层并发;

总结:通常以下列方式来提升可伸缩性:减少锁的持有时间,降低锁的粒度,以及采用非独占的锁或非阻塞锁来代替独占锁。
第12章 并发程序的测试
并发测试大致分为两类:安全性测试与活跃性测试。安全性定义为:不发生任何错误的行为,活跃性定义为:某个良好的行为终究会发生。
性能测试可以通过多方面来衡量:
吞吐量:指一组并发任务中已完成任务所占的比例
响应性:指请求从发出到完成之间的时间(也成延迟)
可伸缩性:指在增加更多资源的情况下(通常指CPU),吞吐量(或者缓解短缺)的提升情况。
另外本章中讲到的一些测试,大多数为并发编程中进行测试,这个在我们实际的编程过程中写main方法进行测试即可,很多测试是在我们编写代码之后必须进行的。另外,一些增加CPU核的测试,或者逐渐增加线程促进资源的大量竞争来进行测试,看系统的性能如何,很多在现在的情况下来看是不太切实际的。这些测试无法完成,但只需要记住测试会出现什么结果,规律即可。
第四部分 高级主题
第13章 显示锁
显示锁(Lock接口)相对于内置锁而言的,主要用到的显式锁为ReentrantLock这个显示锁类。这一章最主要的研究就是这个ReentrantLock锁,关于ReentrantLock
ReentrantLock与synchronized比较:
首先在JAVA5.0时引入ReentrantLock之前,synchronized的缺点:同步没问题,但它并不完美。它有一些功能性的限制:它无法中断一个正在等候获得锁的线程,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况。所以ReentrantLock此时就出现了避免了这些缺陷。它允许把锁定的实现作为Java类,而不是作为语言的特性来实现。这就为Lock的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。
ReentrantLock与synchronized可伸缩性比较:
在Java5.0中,ReentrantLock相比于synchronized有极大的性能优势。但是Java6之后内置锁性能获得极大的提升,就性能、吞吐量而言,两者差别不大。
公平锁与非公平锁:
ReentrantLock 构造器的一个参数是boolean值,它允许您选择想要一个公平(fair)锁,还是一个不公平(unfair)锁。公平锁使线程按照请求锁的顺序依次获得锁;而不公平锁则允许讨价还价,在这种情况下,线程有时可以比先请求锁的其他线程先得到锁。
为什么不让所有的锁都公平呢?毕竟,公平是好事,不公平是不好的,不是吗?在现实中,公平保证了锁是非常健壮的锁,有很大的性能成本。要确保公平所需要的记帐(bookkeeping)和同步,就意味着被争夺的公平锁要比不公平锁的吞吐率更低。作为默认设置,应当把公平设置为 false ,除非公平对您的算法至关重要,需要严格按照线程排队的顺序对其进行服务。
Lock与tryLock:
Lock()是拿不到lock就不罢休,不然线程就一直block。比较无赖的做法。tryLock()马上返回,拿到lock就返回true,不然返回false。比较潇洒的做法。带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false。比较聪明的做法。
轮询锁与定时锁都降低了发生死锁和活锁的可能性。ReentrantLock测试代码附录代码11所示。
什么时候选择用ReentrantLock,什么时候用synchronized
在确实需要一些synchronized所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量。ReentrantLock还具有可伸缩性的好处,应当在高度争用的情况下使用它,大多数synchronized块几乎从来没有出现过争用,所以可以把高度争用放在一边。建议用synchronized开发,直到确实证明synchronized不合适,而不要仅仅是假设如果使用ReentrantLock“性能会更好”。牢记这些是供高级用户使用的高级工具。(真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。
读写锁(ReadWriteLock接口)
分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由JVM自己控制,只要上好相应的锁即可。如果代码只读数据,可以同时读,但不能同时写,此时应该上读锁;如果代码为修改数据,只能有一个线程在写,且不能同时读取,此时上写锁。总之,读时上读锁,写时上写锁,最主要的是ReentrantReadWriteLock这个类的实现。
第14章 构建自定义的同步工具
这一章可以说是翻阅一遍,作为一个大致了解,有一个初步印象对我来说要求就足够了。原因一是自己功力有限还没有达到那个水准,对自己目前来说,并发的内容自己能非常熟练正确地使用并发类库中的工具类就够了,对于我们构建的自定义的同步工具,这是在自己造轮子,所以在没有一定深厚的并发经验之前,先老实做好自己本职的工作,将已有的同步工具用好,等到达到一定的水平之后,再自行构建自定义的同步工具。那时一定会有不一样的想法和观念。
第15章 原子阻塞与非阻塞同步机制
锁的劣势
1是线程在挂起和恢复线程等过程中存在着很大的开销,并且通常存在着较长时间的中断。
2是如果锁的持有时间很短,那么当在不恰当的时间请求锁,会使线程休眠付出很大的代价。
3出现优先级反转(优先级较高的无法获取锁而被挂起),
互斥锁
互斥锁虽然能够保障内存可见性和原子性,保障共享数据的安全访问,但是作为一种较为粗暴的同步机制,有很多缺点:
1是一种悲观锁:如其意,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。(同理乐观锁:就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。)
2上下文切换开销大:线程的挂起和恢复需要很大的开销,并且存在较长时间的中断
3优先级反转:当一个线程在等待一个锁时不能做其他事情,如果持有锁的线程被延迟执行,如发生缺页错误,调度延迟等,那么将导致需要锁的线程无法执行下去,如果被阻塞的线程优先级较高,持有锁的线程优先级较低,将发生优先级反转。
4死锁:两个持有锁的线程都需要申请对方的锁,且该锁为不可放弃的内置锁,那么将发生死锁。
CAS(比较并交换)
CAS是一种乐观锁,多个线程尝试使用CAS更新同一个变量时,只有其中一个线程能够更新变量的值,其它线程都将失败,失败的线程并不会挂起。
原子操作与互斥锁、volatile的比较
与锁相比较
原子变量比锁的粒度更加细、量级更轻,这对于在多处理器系统上实现高性能的并发代码是非常关键的,原子变量将发生竞争的范围缩小到单个变量上,这是你获得的粒度最细的情况,多个线程之间不会因为竞争而发生上下文切换的情况。
与volatile比较
原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的“读–改–写”操作。比如AtomicInteger表示一个int类型的值,它提供了get和set方法与volatile类型的int变量在读取和写入上有着相同的内存语义,它还提供了原子的compareAndSet方法、以及原子的添加、递增和递减方法。
此外,要注意到,任何一个真实的程序都不会除了竞争锁或原子变量,其他什么工作都不做。在实际情况中,原子变量在可伸缩性上要高于锁,因为在应对常见的竞争程度时,原子变量的效率更高。反过来,在高度竞争的情况下,锁的性能将超过原子变量的性能。锁与原子变量在不同程度竞争上的性能说明了各自的优势和劣势
非阻塞的同步机制
非阻塞算法:就是用底层的原子机器指令,如比较并交换(CAS)代替锁来确保数据在并发访问中的一致性。与基于锁的方案相比,非阻塞算法在设计和实现上都要更加复杂,但是它们在可伸缩性和活跃性上都有很大的优势。使用原子变量类来构建非阻塞算法是最常用的手段

第16章Java内存模型

图16.1 Java内存模型
上图是上次EJ笔记中画的一个内存模型的UML图,在此方面起见,附上。
开源链接是https://www.processon.com/view/link/590ed5e7e4b014dc3acaaa64
首先
什么是内存模型,为什么需要它?
1平台的内存模型
在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且定期地与主内存进行协调。在不同的处理器架构中提供了不同级别的缓存一致性,其中一部分只提供最小的保证,即允许不同的处理器在任意时刻从一个存储位置上看到不同的值。
2重排序
重排序,说简单一点,就是CPU为了提高运行的效率性能,将代码运行得到顺序进行调整以满足CPU的使用习惯。所以这会对没有同步的多线程造成隐患。多个线程同时操作同一个变量时,读与写必须保证原子性,否则会出现读取的变量值并不是最新修改的等重排序问题。
3借助同步
主要是Happens-Before规则,Happens-Before的规则包括:
1程序顺序规则。如果程序中操作A在操作B之前,那么在线程中A操作将在B操作之前执行。
2监视器锁规则。在监视器锁上的解锁操作必须在同一个监视器上的加锁操作之前执行。
3Volative变量规则。对Volative变量的写入操作必须在对该变量的读操作之前执行。
4线程启动规则。在线程上对Thread.Start的调用必须在线程中执行任何操作之前执行。
5线程结束规则。在线程中的任何操作都必须在其他线程检测到该线程已结束之前执行,或者从Thread.join中成功返回,或者调用Threas.isAlive时返回false。
6中断规则。当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行(通过抛出InterruptedException,或者调用isInterrupted和interrupted)。
7终结器规则。对象的构造函数必须在启动该对象的终结器之前执行完成。
8传递性。如果操作A在操作B之前执行,并且操作B在操作C之前执行,则操作A必须在操作C之前执行。
发布
主要是对象初始化的问题,单例模式特别多,如果没有同步,会造成不安全地发布,同时,同步也要考虑到性能问题,

总结
首先全书的重点章节自己感受为第5章基础构建模块,里面的并发工具类必需熟练掌握;第6章任务执行,其中的Executor框架以及其衍生的相关知识点;第10章避免活跃性危险部分,死锁内容等;第13章显式锁ReentrantLock这个类;第16章Java内存模型,有助于更好地理解底层知识。
并不是说其他章节不重要,只是对自己来说,这些较为充分地扩展了自身的知识,有不懂的一般都写了测试代码运行测试,略微吐槽一下本书的缺点,就是绝大多数程序没有测试代码,只能对照文字自行看、读、自行理解体会,且没有反馈结果,所以就造成很多类自行写一些测试代码,且不好写,但又无法确定有些测试代码是否满足要求(功力未到的缘故),附录代码只是作为一个展示的部分,很多代码没有展示具体在Github保存:
https://github.com/Wsky51/ConcurrencyCode
另外,对于第9章的并发图形编程还有第14章的构建自定义同步工具,相对来讲读的不那么深的部分也在相应章节做了说明,一是时间,二是必要性,三是能力和精力。此外,在文档结构上,将代码部分作为附录单独拿出来,避免混在在文档中显得臃肿。
总之,读着本书,很多必须自行写代码测试(貌似看所有书都是这样),但读这本书这种感想尤为强烈,不写测试代码无法理解无法继续读下去。此外,一些文字上的可能由于翻译问题说的不是那么直白,结合网络博客等资源也能更好的理解。

下一步阅读计划任务:
《重构,改善既有代码的设计》6月8号之前做完读书笔记
《Java与模式(阎宏)》6月22号之前做完读书笔记

整本书思维导图如下:
链接:https://www.processon.com/view/link/5926bdede4b01fec94922ff0

附录代码
代码1(容易忽略同步造成Exc)附录:


package com.wy.concurrency;

import java.util.Collections; import java.util.HashSet; import java.util.Random; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;

/**  * Created by LENOVO on 2017/5/20.  */

public class HiddenIterator {
    private final Set<Integer> set1=new HashSet <Integer>();
    private final Set<Integer> set= new HashSet <>();

    public synchronized void add(Integer i){
        set.add(i);
    }
    public synchronized void remove(Integer i){
        set.remove(i);
    }

    public void addTenThings(){
        Random r=new Random();
        for (int i=0;i<10;i++){
            add(r.nextInt()); //            System.out.println("循环体里面");
        }
        System.out.println("输出结果是"+set); //        System.out.println("-----外部------");
    }


    public static void main(String[] args) {
        final HiddenIterator demo=new HiddenIterator();
        ExecutorService es=Executors.newCachedThreadPool();
        for (int i=0;i<10;i++){
            es.execute(new Runnable() {
                @Override
                public void run() {
                    demo.addTenThings();
                }
            });
        }
        es.shutdown();
    } } OUTPUT:

代码2(阻塞队列以及闭锁)附录:

package com.wy.concurrency;

import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class BlockingQueueTest {
    private static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(5, true); //最大容量为5的数组堵塞队列
    //private static LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(5);

    private static CountDownLatch producerLatch; //倒计时计数器
    private static CountDownLatch consumerLatch;

    public static void main(String[] args) {
        BlockingQueueTest queueTest = new BlockingQueueTest();
        queueTest.test();
    }

    private void test(){
        producerLatch = new CountDownLatch(10); //state值为10
        consumerLatch = new CountDownLatch(10); //state值为10

        Thread t1 = new Thread(new ProducerTask());
        Thread t2 = new Thread(new ConsumerTask());

        //启动线程
        t1.start();
        t2.start();

        try {
            System.out.println("producer waiting...");
            producerLatch.await(); //进入等待状态,直到state值为0,再继续往下执行
            System.out.println("producer end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            System.out.println("consumer waiting...");
            consumerLatch.await(); //进入等待状态,直到state值为0,再继续往下执行
            System.out.println("consumer end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //结束线程
        t1.interrupt();
        t2.interrupt();

        System.out.println("end");
    }
    //生产者
    class ProducerTask implements Runnable{
        private Random rnd = new Random();

        @Override
        public void run() {
            try {
                while(true){
                    queue.put(rnd.nextInt(100)); //如果queue容量已满,则当前线程会堵塞,直到有空间再继续

                    //offer方法为非堵塞的
                    //queue.offer(rnd.nextInt(100), 1, TimeUnit.SECONDS); //等待1秒后还不能加入队列则返回失败,放弃加入
                    //queue.offer(rnd.nextInt(100));

                    producerLatch.countDown(); //state值减1
                    //TimeUnit.SECONDS.sleep(2); //线程休眠2秒
                }
            } catch (InterruptedException e) {
                //e.printStackTrace();
            }  catch (Exception ex){
                ex.printStackTrace();
            }
        }
    }
    //消费者
    class ConsumerTask implements Runnable{
        @Override
        public void run() {
            try {
                while(true){
                    Integer value = queue.take(); //如果queue为空,则当前线程会堵塞,直到有新数据加入

                    //poll方法为非堵塞的
                    //Integer value = queue.poll(1, TimeUnit.SECONDS); //等待1秒后还没有数据可取则返回失败,放弃获取
                    //Integer value = queue.poll();

                    System.out.println("value = " + value);

                    consumerLatch.countDown(); //state值减1
                    TimeUnit.SECONDS.sleep(2); //线程休眠2秒
                }
            } catch (InterruptedException e) {
                //e.printStackTrace();
            } catch (Exception ex){
                ex.printStackTrace();
            }
        }
    }

}

OUTPUT:

代码3(闭锁CounotDownLatch)附录:

package com.wy.concurrency;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * Created by LENOVO on 2017/5/23.
 */
//用与理解和栅栏的区别,5人跑步同时开始起跑,最后一个人跑完后才结束
public class CountDownLatchRaceDemo {
    //下面是闭锁CountDownLatch的测试代码
    public static void main(String[] args) {
        CountDownLatch begin=new CountDownLatch(1);
        CountDownLatch end=new CountDownLatch(5);
//        begin.countDown();
        for (int i=0;i<5;i++){
            new Thread(new Runner(i,begin,end)).start();
        }
        long be = System.nanoTime();
        System.out.println("judge:3,2,1,Race begin!!! ");
        begin.countDown();
        try {
            end.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            long endtime=System.nanoTime();
            System.out.println("judge:所有人已经到达");
            System.out.println("从开始比赛到最后一名到达花费时间"+(endtime-be));
        }
    }
}
class Runner implements Runnable{
    private final int id;
    private final CountDownLatch begin;
    private final CountDownLatch end;
    public Runner(final int id,final CountDownLatch begin,final CountDownLatch end){
        this.id=id;
        this.begin=begin;
        this.end=end;
    }

    @Override
    public void run() {
        try {
            begin.await();
            System.out.println(id+"号运动员开跑了");
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println(id+"号运动员跑过终点");
            end.countDown();
        }
    }
}

OUTPUT:

代码4(FutureTask简单实例)附录:

package com.wy.concurrency;

import java.util.Random;
import java.util.concurrent.*;

/**
 * Created by LENOVO on 2017/5/23.
 */
public class FutureTaskTest {
    public static void main(String[] args) {
        ExecutorService es= Executors.newCachedThreadPool();
        Future <Integer> future = es.submit(new Callable <Integer>() {
            @Override
            public Integer call() throws Exception {
                return new Random().nextInt();
            }
        });
        try {
            Integer integer = future.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        finally {
            es.shutdown();
        }
    }
}

OUTPUT:

代码5(Semaphore信号量)附录:

package com.wy.concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * Created by LENOVO on 2017/5/23.
 */
public class SemaphoreTest {
    public static void main(String[] args) {

        ExecutorService es= Executors.newCachedThreadPool();
        final Semaphore semp=new Semaphore(5);//只能由5个线程访问
        for (int i=0;i<20;i++){
            final int index=i;
            es.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        semp.acquire();
                        System.out.println("Accessing"+index);
                        Thread.sleep(100);
                        semp.release();//访问完成后释放
                        System.out.println("---------------"+index+"已访问完毕"+semp.availablePermits());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        es.shutdown();

    }
}

OUTPUT:

代码6(Semaphore信号量)附录:

package com.wy.concurrency;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Created by LENOVO on 2017/5/23.
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        final int count =5;
        final CyclicBarrier barrier=new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("通过栅栏,做其他的事");
            }
        });
        for (int i=0;i<5;i++){
            new Thread(new NewRunner(i,barrier)).start();
        }
    }
}
class NewRunner implements Runnable{
    private final int id;
    private final CyclicBarrier barrier;
    public NewRunner(final int id,final CyclicBarrier barrier){
        this.id=id;
        this.barrier=barrier;
    }

    @Override
    public void run() {
        System.out.println(id+"运动员开跑了");
        try {
            Thread.sleep(2000);
            System.out.println(id+"运动员到达终点");
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

OUTPUT:

代码7(ExecutorService.invokeAll()的使用)附录:

package com.wy.concurrency;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

/**
 * Created by LENOVO on 2017/5/23.
 */
public class InvokeAllThread {
//固定线程池的大小
    static ExecutorService es= Executors.newFixedThreadPool(5);
    private class QuoteTask implements Callable<BigDecimal>{
        public final double price;
        public final int num;
        public QuoteTask(double price,int num){
            this.price=price;
            this.num=num;
        }

        @Override
        public BigDecimal call() throws Exception {
            Random random=new Random();
            int sleeptime = random.nextInt(10) + 1;
            TimeUnit.SECONDS.sleep(sleeptime);
            BigDecimal d=BigDecimal.valueOf(price*num).setScale(2,BigDecimal.ROUND_HALF_UP);
            System.out.println("耗时"+sleeptime+"s,单价是:"+price+",人数是:"+num+",总额是:"+d);
            return d;
        }
    }
    /**
     * 在规定的时间内获得所有旅游团的报价
     *
     */
    public void getRankedTraQuote() throws InterruptedException{
        List<QuoteTask> tasks=new ArrayList <>();
        //模拟计算10个旅游团的报价
        for (int i=1;i<10;i++){
            tasks.add(new QuoteTask(200,i));
        }
        //使用InvokeAll方法批量限时调用所有任务,预计10秒内所有任务执行完毕,没有执行完毕的自动取消

        List<Future<BigDecimal>> futures=es.invokeAll(tasks,15,TimeUnit.SECONDS);
        //报价的List集合
        List<BigDecimal> totalPriceList=new ArrayList <>();
        Iterator <QuoteTask> iterator = tasks.iterator();
        for (Future<BigDecimal> future:futures){
            QuoteTask task = iterator.next();
            try {
                totalPriceList.add(future.get());
            } catch (ExecutionException e) {
                System.out.println("失败");
                e.printStackTrace();
            }
        }

        for (BigDecimal bigDecimal:totalPriceList){
            System.out.println(bigDecimal);
        }
        es.shutdown();
    }

    public static void main(String[] args) {
        InvokeAllThread invokeAllThread=new InvokeAllThread();
        try {
            invokeAllThread.getRankedTraQuote();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

OUTPUT:

代码8(FutureTask.get(time,TimeUnit)超时示例)附录:

package com.wy.concurrency;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

/**
 * Created by LENOVO on 2017/5/23.
 */

/**
 *用来测试FutureTask.get(long,TimeUnit)在指定时间失效的方法
 *
 * <p>这个测试类进行模拟设计网络界面中加载广告的过程</p>
 *
 * @author WuYi
 * @Since 17年5月23日
 */
public class TimeOutThreadDemo {
    /**
     * 创建一个线程池,固定大小为5,默认的加载时间为4s,超过时间广告将会以默认的广告界面加载
     */
    private static ExecutorService es= Executors.newFixedThreadPool(5);
    private final static long timeout =4;

    /**
     * 加载广告,如果传入相应的界面后加载广告的时间大于默认的时间{@code 4s}那么该方法将自动加载一个默认的广告
     *
     * @param pageTitle 页面的名称
     * @throws InterruptedException 如果{@code futureTask被迫中断}
     * @throws ExecutionException 如果出现执行时的异常
     * @throws TimeoutException 如果超时
     *
     */
    public static void loadAdByPage(final String pageTitle){
        String page;
        FutureTask<String> futureTask=new FutureTask <String>(new Callable <String>() {
            @Override
            public String call() throws Exception {
                System.out.println("开始加载广告信息");
                int loadtime=new Random().nextInt(5)+1;
                TimeUnit.SECONDS.sleep(loadtime);
                System.out.println("正常加载广告完毕,耗时"+loadtime);
                return pageTitle;
            }
        });
        es.submit(futureTask);
        //在预定时间4s内进行等待
        System.out.println("预期任务执行完时间"+timeout+"s");
        try {
            page=futureTask.get(timeout,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            page="出现中断,将加载默认广告";
        } catch (ExecutionException e) {
            page="任务执行异常,将加载默认广告";
        } catch (TimeoutException e) {
            page="任务执行超时了,显示默认的广告页面";
            futureTask.cancel(true);
        }
        System.out.println("成功加载广告页面:"+page);
    }

    public static void main(String[] args) {
        List<String> titleList=new ArrayList <String>();
        titleList.add("体育");
        titleList.add("娱乐");
        titleList.add("国际");
        titleList.add("国内");
        titleList.add("社会");
        for (String pageTitle:titleList){
            loadAdByPage(pageTitle);
        }
        es.shutdown();
        while (true){
            if (es.isTerminated()){
                System.out.println();
                System.out.println("所有任务执行完毕,关闭线程池");
                break;
            }
        }
    }
}

OUTPUT:

代码9(ThreadLocal获取数据库连接Conn)附录:

package com.wy.concurrency;

import java.sql.*;

/**
 * Created by LENOVO on 2017/5/18.
 */
public class ThreadLocalDemo {
    private final static String driverName="oracle.jdbc.driver.OracleDriver";
    private final static String url="jdbc:oracle:thin:@localhost:1521:orcl";
    private final static String username="WUYI";
    private final static String password="Wuyi1482675166";

    private static ThreadLocal<Connection> connectionHolder=new ThreadLocal<Connection>(){
        public Connection initialValue() {
            try {
                return DriverManager.getConnection(url);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;
        }
    };
    public static Connection getConnectin(){
        return connectionHolder.get();

    }
    public static void main(String[] args) {
        try {
            Class.forName(driverName);
            Connection conn = DriverManager.getConnection(url, username, password);
            PreparedStatement preparedStatement = conn.prepareStatement("SELECT * FROM USERS");
            ResultSet resultSet = preparedStatement.executeQuery();
            while (resultSet.next()){
                System.out.println(resultSet.getInt("USERID")+" "+resultSet.getString("username"));
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        catch (SQLException e) {
            e.printStackTrace();
        }

    }
}

代码10(锁顺序死锁)附录:

package com.wy.concurrency;

/**
 * Created by LENOVO on 2017/5/21.
 */
public class LeftRightDeadlock implements Runnable {
    private final Object left = new Object();
    private final Object right = new Object();
    public void doSomething() {
        System.out.println("做些事");
    }

    public void doSomethingelse() {
        System.out.println("做些其他事");
    }

    public void leftRight() {
        synchronized (left) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (right) {
                doSomething();
            }
        }
    }

    public void rightLeft() {
        synchronized (right) {
//            try {
//                Thread.sleep(100);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            synchronized (left) {
                doSomethingelse();
            }
        }
    }

    @Override
    public void run() {
        leftRight();
    }

    public static void main(String[] args) {
        final LeftRightDeadlock demo = new LeftRightDeadlock();
        final LeftRightDeadlock demo2 = new LeftRightDeadlock();
//        ExecutorService es = Executors.newCachedThreadPool();
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                demo.leftRight();
            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                demo.rightLeft();
            }
        });
            t1.start();
            t2.start();

//        es.shutdown();
    }
}

OUTPUT:
出现死锁

代码11(锁顺序死锁)附录:

package com.wy.concurrency;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by LENOVO on 2017/5/24.
 */
public class ReentrantLockDemo {
    private ReentrantLock lock = new ReentrantLock();

    public void untimed() {
        boolean captured = lock.tryLock();
        try {
            System.out.println("tryLock()" + captured);
        } finally {
            if (captured) {
                lock.unlock();
            }
        }
    }

    public void timed() {
        boolean captured = false;
        try {
            captured = lock.tryLock(2, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            System.out.println("tryLock(2, TimeUnit.SECONDS)"+captured);
        }
        finally {
            if (captured) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        final ReentrantLockDemo demo=new ReentrantLockDemo();
        demo.untimed();
        demo.timed();
        new Thread(){
            {
                setDaemon(true);
            }
            @Override
            public void run() {
//                try {
                    demo.lock.lock();
                    System.out.println("acquired");
//                } finally {
//                    demo.lock.unlock();
//                }
            }
        }.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        demo.untimed();
        demo.timed();


    }
}

OUTPUT:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值