Java并发学习3

六.读写锁

ReadWriteLock是jdk5中提供的读写分离锁。读写分离锁可以有效的帮助减少锁竞争,以提升性能。用锁分离的机制来提升性能非常容易理解,比如线程A1,A2,A3进行写操作,B1,B2,B3进行读操作,如果使用重入锁或者内部锁,则理论上说所有读之间、读和写之间、写和写之间都是串行操作。当B1进行读取时,B2,B3则需要等待锁的释放。由于读操作并不对数据的完整性造成破坏,这种等待显然是不合理。因此,读写锁就有了发挥功能的余地。

  在这种情况下,读写锁允许多个线程同时读,使得B1,B2,B3之间真正并行。但是,考虑到数据完整性,写写操作和读写操作间仍然是需要相互等待和持有锁的。总的来说,读写锁的访问约束如下:

  如果在系统中,读操作次数远远大于写操作,则读写锁就可以发挥最大的功效,提升系统的性能。

一个例子


七.几个多线程控制工具类

倒计时器 CountDownLatch

  Java的concurrent包里面的CountDownLatch其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。

  你可以向CountDownLatch对象设置一个初始的数字作为计数值,任何调用这个对象上的await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为0为止。

  CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。

  举个例子,有三个工人在为老板干活,这个老板有一个习惯,就是当三个工人把一天的活都干完了的时候,他就来检查所有工人所干的活。记住这个条件:三个工人先全部干完活,老板才检查。所以在这里用Java代码设计两个类,Worker代表工人,Boss代表老板,具体的代码实现如下:


输出结果: 
王二正在干活! 
李四正在干活! 
老板正在等所有的工人干完活…… 
张三正在干活! 
张三活干完了! 
王二活干完了! 
李四活干完了! 
工人活都干完了,老板开始检查了!

循环栅栏 CyclicBarrier

与CountDownLatch功能类似,但是比它强大。

CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。 
CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。 
CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

线程阻塞工具类 LockSupport

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。 
LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。 
因为park() 和 unpark()有许可的存在;调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性。

说明:park和wait的区别。wait让线程阻塞前,必须通过synchronized获取同步锁。


八.线程复用之线程池

什么是线程池

频繁使用new Thread来创建线程的方式并不太好。因为每次new Thread新建和销毁对象性能较差,线程缺乏统一管理。好在Java提供了线程池,它能够有效的管理、调度线程,避免过多的资源消耗。优点如下:

重用存在的线程,减少对象创建、销毁的开销。 
可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。 
提供定时执行、定期执行、单线程、并发控制等功能。 
线程池原理简单的解释就是会创建多个线程并且进行管理,提交给线程的任务会被线程池指派给其中的线程进行执行,通过线程池的统一调度、管理线程池的统一调度、管理使得多线程的使用更简单高效。

线程池负责管理工作线程,包含一个等待执行的任务队列。线程池的任务队列是一个Runnable集合,工作线程负责从任务队列中取出并执行Runnable对象。

jdk对线程池的支持

为了更好的控制多线程,jdk提供了一套Executor框架,帮助开发人员有效的进行线程控制,其本质就是一个线程池。

以上成员都在java.util.concurrent包下,是jdk并发包的核心类。其中ThreadPoolExecutor表示一个线程池。Executors类则扮演者线程池工厂的角色,通过Executors可以取得一个拥有特定功能的线程池。Executors主要提供了以下方法:

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。 
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

newFixedThreadPool示例

newScheduledThreadPool示例

ScheduledExecutorService有两个方法,scheduleAtFixedRate和scheduleAtFixedDelay,注意区分。

ThreadPoolExecutor

其实核心的几个线程池,其内部都是使用了ThreadPoolExecutor来实现。即他们只是ThreadPoolExecutor类的封装。下面我们来看一下ThreadPoolExecutor:

用给定的初始参数和默认的线程工厂及处理程序创建新的 ThreadPoolExecutor。使用 Executors 工厂方法之一比使用此通用构造方法方便得多。

参数:

corePoolSize - 池中所保存的线程数,包括空闲线程。

maximumPoolSize - 池中允许的最大线程数。

keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

unit - keepAliveTime 参数的时间单位。

workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。

threadFactory - 执行程序创建新线程时使用的工厂。

handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

线程的创建和销毁

线程池的基本大小(corePoolSize)、最大大小(maximumPoolSize)以及存活时间等因素共同负责线程的创建与销毁。

基本大小也是线程池的目标大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。

最大大小表示可同时活动的线程数量的上限。

如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过基本大小时,这个线程将被终止。

管理队列任务

ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务。基本的任务排队方法有3种:有界队列, 无界队列, 同步移交(Synchronous Handoff)。

有界队列饱和策略

有界队列被填满后,饱和策略开始发挥作用。饱和策略可以通过调用setRejectedExecutionHandler来修改。jdk提供了几种不同的RejectedExecutionHandler实现:AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy 。

AbortPolicy:默认的饱和策略。抛出 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。

CallerRunsPolicy: 不抛弃任务,不抛出异常,而将任务退回给调用者。

DiscardOldestPolicy:放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。

DiscardPolicy:默认情况下它将放弃被拒绝的任务。

扩展ThreadPoolExecutor

ThreadPoolExecutor是可扩展的,它提供了几个可以在子类化中改写的方法:beforeExecute、afterExcute和terminated。在这些方法中,还可以添加日志、计时、监视或统计信息收集的功能。

无论任务是从run中正常返回,还是会抛出一个异常在而返回,afterExcute都会被调用。(如果任务执行完成后带有Error,那么就不会调用afterExcute)。

如果beforeExecute抛出一个RuntimeException,那么任务将不被执行,并且afterExcute也会被调用。

在线程池完成关闭操作时调用terminated,也就是在所有任务都已经完成并且所有工作者线程也已经关闭后,terminated可以释放Executor在其生命周期里分配的各种资源,此外还可以执行发送通知,记录日志或者收集finalize统计信息等操作。

阅读更多

扫码向博主提问

去开通我的Chat快问

weixin_39816740

非学,无以致疑;非问,无以广识
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

Java并发学习3

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭