Java并发编程的艺术6之Java中的13个原子操作类、并发工具类、线程池、Executor框架

Java从JDK1.5开始提供了java.util.concurrent.atomic包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新变量地方式。

6.Java中的13个原子操作类

6.1.原子更新基本类型

使用原子方式更新基本类型,Atomic包提供了三个类:AtomicBoolean、AtomicInteger、AtomicLong。

几种常用方法: int addAndGet(int delta)、boolean compareAndSet(int expect,int update)、int getAndTncrement()、void laszSet()、int getAndSet().

Atomic包里的类基本都是使用Unsafe实现的。

6.2.原子更新数组

通过原子的方式更新数字里的某个元素,Atomic包提供了以下4个类:

AtomicIntegerArray:原子更新整型数组里的元素。

AtomicLongArray:原子更新长整型数组里的元素。

AtomicReferenceArray:原子更新引用类型数组里的元素。

6.3.原子更新引用类型

如果要原子更新多个变量,就需要使用这个原子更新类型提供的类:

AtomicReference:原子更新引用类型

AtomicReferenceFieldUpdater:原子更新引用类型里的字段

AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记为和引用类型。

6.4.原子更新字段类

三个类:AtomicIntegerFieldUpdater:原子更新整形字段的更新器

AtomicLongFieldUpdater:原子更新长整形字段的更新器

AtomicStampedReference:原子更新带有版本号的引用类型该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新可能出现的ABA问题。

要想原子的更新字段类需要两步:1.因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdate()创建一个更新器,并且需要设置想要更新的类和属性。2.更新类的字段(属性)必须使用public volatile修饰符。

7 Java中的并发工具类

在JDK开发包里提供的几种有效的并发工具类,CountDownLatch,CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了在线程间交换数据的一种手段。

7.1 等待多线程完成的CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作

7.2 同步屏障 CyclicBarrier

CyclicBarrier:让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

默认构造方法:CyclicBarrier(int parties) 参数表示屏障拦截的线程数量。

更高级的构造函数:CyclicBarrier(int parties,Runnable barrierAction)用于在线程到达屏障时,有限执行barrierAction,方便处理更复杂的业务场景。

CyclicBarrier可以用于多线程计算数据场景。

CyclicBarrier和CountDownLatch的区别:CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier可以处理更为复杂的业务场景。CyclicBarrier还有其他方法:getNUmberWaiting方法可以获取CyclicBarrier阻塞的线程数量。isBloken方法用来了解阻塞的线程是否被中断。

7.3 控制并发线程数的Semaphore

Semaphore 用来控制同时访问特定资源的线程数量。它通过协调各个线程,以保证合理的使用公共资源。

应用场景在于流量控制,特别是公共资源有限的应用场景,比如数据库连接。

构造方法Semaphore(int permits)接受一个整形数字,表示可用的许可证数量。首先线程使用Semphore的acquire方法获取一个许可证,使用完之后调用release方法归还许可证。还可以用tryAcquire方法获取许可证。

Eemaphore还有其他方法:intavailablePermits()返回此信号量中当前可用的许可证数。

intgetQueueLength()返回正在等待获取许可证的线程数。

booleanhasQueueThreads:是否有线程正在等待获取许可证。

7.4 线程间交换数据的EXchanger

Exchanger是一个用于线程间协作的工具类,用于线程间的数据交换。通过提供一个同步点,使得在这个同步点,两个线程可以交换彼此的数据,通过exchange()方法。第一个线程执行exchange方法,然后会一直等待第二个线程也执行exchange方法,当两个线程都达到同步点时,这两个线程就可以交换数据。

应用场景:遗传算法、校对工作。

8Java中的线程池

合理使用线程池有3个好处:

1.降低资源消耗 2.提高相应速度 3.提高线程的可管理性。

8.1 线程的实现原理

看书上的流程图

8.2 线程池的使用

8.2.1 线程池的创建

使用ThreadPoolExecutor ,new  ThreadPoolExecutor(coolPoolSize,maxiumPoolSize,keepAliveTime,milliseconds,runnableTaskQueue,handler);

(1)coolPoolSize :线程池的基本大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

(2)runnableTaskQueue:任务队列,用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlovkingQueue。

(3)maximumPoolSize 线程池最大数量

如果使用了无界的任务队列这个参数就没什么效果。

(4)ThreadFactory用于设置创建线程的工厂,可以给线程起名字。

(5)RejectedExecutionHandler 饱和策略。当队列和线程池都满了后,采取的处理新提交任务的策略。一共有4种:AbortPolicy,CallerRunsPolicy,DiscardOldestPolicy,DiscardOilicy.

8.2.2 向线程池提交任务

两个方法execute(不需要返回值)和submit(需要返回值)。submit方法中,线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get方法来获取返回值,get方法会阻塞当前线程直到任务完成。

8.2.3 关闭线程池

shutdown或shutdownNow。原理:遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应终端的任务可能永远无法中止。

shutdown:只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

shutdownNow:首先将线程池的状态设置成STOP,然后尝试停止所有正在执行或暂停任务的线程,并返回等待执行任务的列表。

调用后,isShutDown方法会返回true,只有所有任务都关闭后,才表示线程池关闭成功,调用isTerminaed方法会返回true。

通常调用shutdown方法,如果线程不用执行完,调用shutdownNow方法。

8.2.4 合理地配置线程池

需要先分析任务特性,看书上截图

建议使用有界队列。

8.2.5 线程池的监控

taskCount:线程池需要执行的任务数量

completedTaskCount:线程池在运行过程中已完成的任务数量。

largestPoolSize:线程池曾经创建过的最大线程数量《=taskCount。

getPoolSize:线程池的线程数量

getActiveCount:获取活动的线程数。

9 Executor框架

Java的线程即是工作单元也是执行机制。从JDK1.5开始,把工作单元与执行机制分离开。工作单元包括Runnable和Callable,而执行机制由Executor提供。

9.1 Executor框架简介

 在上层,Java多线程程序通常会把应用分解为若干个认为,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在底层,操作系统将这些线程映射到硬件处理器上。

Executor框架的结构:主要由3大部分组成:

  • 任务: 包括被执行任务需要实现的接口:Runnable接口或Callable接口。
  • 任务的执行:包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口。(ThreadPoolExecutor和ScheduledThreadPoolExecutor)
  • 异步计算的结果 包括接口Future和实现Future接口的FutureTask类。

Executor框架的成员:ThreadPoolExecutor、ScheduledThreadPoolExecutor、Future接口、Runnable接口、Callable接口和Executors。

(1)ThreadPoolExecutor (参数是线程池的创建中的参数)

通常使用工厂类Executors来创建,可以创建3种类型的ThreadPoolExecutor:

  • FixedThreadPool(适用于需要限制当前线程数量的场景,适合负载比较重的服务器)、

它的coolPoolSize,maxiumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。当线程池中的线程数大于coolPoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。keepAliveTime=0L,意味着多余的空闲线程立刻被终止。(看书上的流程图)

  • SingleThreadExecutor (适合需要保证顺序的执行各个任务,并且在任意时间点都不会有多个线程活动的应用场景)

它的coolPoolSize,maxiumPoolSize都被设置为1.其他的与FixedThreadPool相同。使用无界队列LinkedBlockingQueue作为线程池的工作队列。(看书上的流程图)

  • CachedThreadPool(大小无界,适合执行很多的异步短期任务的小程序,或者负载较轻的服务器)

CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,且maxiumPoolSize无界,意味着如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会由于创建过多的线程而耗尽CPU和内存资源。

(仔细揣摩下流程图)

(2)ScheduledThreadPoolExecutor

继承自ThreadPoolExecutor,通常使用工厂类Executors来创建,可以创建2种类型的ScheduledThreadPoolExecutor:ScheduledThreadPoolExecutor(包含若干个线程,适用于多个后台线程执行周期任务,同时为了满足资源管理的需求而限制后台线程数量的应用场景)、SingleThreadScheduledExecutor(包含一个线程,且需要保证顺序的执行各个任务)

ScheduledThreadPoolExecutor功能与Time类似,但ScheduledThreadPoolExecutor的功能更加强大,灵活。Time如对应的是单个后台线程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。

  • ScheduledThreadPoolExecutor的运行机制

基于DelayQueue,是个无界队列,所以maximumPoolSize在这里没有什么用处。

它的执行主要分为两个部分:

        1)当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDealy()方法时,会向Delayqueue添加一个实现了RunnableScheduledFuture接口的ScheduleFutureTask。

         2)线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务

  • ScheduledThreadPoolExecutor的实现

ScheduleFutureTask(待调度任务)会被放到一个Delayqueue中,待调度任务主要包含3个成员变量,long型time,表示这个任务将要被执行的具体时间。long型sequenceNumber,表示这个任务被添加到ScheduledThreadPolExecutor中的序号。

long型period,表示任务执行的间隔周期。

DelayQueue封装了一个PriorityQueue,它会对队列中的ScheduledFutureTask进行排序,首先比较time小的,在前面,time相同就比较sequenceNumber,sequenceNumber小的在前面。

 

 

 

(3)Future接口和实现Future接口的FutureTask类

Future接口和实现Future接口的FutureTask类表示异步计算的结果。

当我们把Runnable接口或Callable接口的实现类(submit)给ThreadPoolExecutor或ScheduledThreadPoolExecutor时,ThreadPoolExecutor或ScheduledThreadPoolExecutor会返回一个FutureTask对象。到目前最新的JDK1.8中Java通过上述API返回的是一个FutureTask对象。但这个仅仅是一个实现了Future的接口,所以以后的JDK中,返回的不一定是FutureTask对象。

FutureTask类除了实现Future接口,还实现了Runnable接口,所以FutureTask可以较给executor执行,也可以调用线程直接执行(FutureTask.run())。根据FutureTask.run()被执行的时机,FutureTask可以分为以下三种状态:

        1)未启动。FutureTask.run()还没有被执行,FutureTask处于未启动状态。

         2)已启动。FutureTask.run()被执行的过程中,FutureTask处于已启动状态。

          3)已完成。FutureTask.run()方法执行完后正常结束,或被取消,或抛出异常而结束,FutureTask处于已完成状态。

当FutureTask处于未启动或已启动状态时,执行FutureTask.get()方法将导致线程阻塞,执行FutureTask.cancel(true)方法将以中断执行此任务线程的方式来试图停止任务。处于已完成状态时,执行FutureTask.get()方法会导致调用线程立即返回结果或抛出异常,执行FutureTask.cancel(。。。)方法会返回false。已启动状态时,执行FutureTask.cancel(false)方法将不会对正在执行此任务的线程产生影响。

FutureTask的使用:一个线程需要等待另一个线程把某个任务执行完成后它才能继续执行。

FutureTask的实现:基于AbstractQueuedSynchronizer(AQS)。AQS是一个同步框架,它提供通用机制来原子性管理同步状态、阻塞或唤醒线程以及维护被阻塞线程的队列。基于AQS的同步器会包含两种类型的操作:acquire操作和release操作。

FutureTask.run()的执行过程:1)执行在构造函数中指定的任务 2)以原子方式类更新同步状态,调用AQS。compareANdSetState(int expect,int update)设置state为执行完成状态RAN)。如果这个原子操作成功,就设置代表计算劫夺的result变量为Callable.call()的值,然后调用AQS.releaseShared(int   arg)。3)AQS.releaseShared(int arg)首先会回调在子类Sync中实现的tryReleaseShared(arg)来执行release操作(设置运行任务的线程runner为null,然后返回true);AQS.releaseShared(int arg),然后唤醒线程等待队列中的第一个线程。4)调用FutureTask.done()

 

 

 

 

(4)Runnable接口或Callable接口:

Runnable接口或Callable接口的实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。区别是Runnable不会返回结果,Callable可以返回结果。

除了可以自己创建实现Callable接口的对象外,还可以使用工厂类Executors来把一个Runnable包装成Callable。

9.2 ThreadPoolExecutor 详解

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值