java并发包java.util.concurrent介绍

本文介绍了Java并发包`java.util.concurrent`的核心组件,包括阻塞队列BlockingQueue的实现如ArrayBlockingQueue、LinkedBlockingQueue等,ConcurrentHashMap的并发性能优势,以及并发工具类如CountDownLatch、CyclicBarrier、Semaphore、Exchanger和Lock等。同时,讲解了ExecutorService、ThreadPoolExecutor和ScheduledExecutorService的线程池管理,以及ForkJoinPool和读写锁ReadWriteLock的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java.util.concurrent是并发包的目录。

阻塞队列 BlockingQueue接口通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。该接口的具体实现有ArrayBlockingQueue,DelayQueue,LinkedBlockingQueue,PriorityBlockingQueue,SynchronousQueue。

DelayQueue 对元素进行持有直到一个特定的延迟到期。注入其中的元素必须实现java.util.concurrent.Delayed 接口,DelayQueue 将会在每个元素的 getDelay() 方法返回的值的时间段之后才释放掉该元素。

ArrayBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。

LinkedBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。

所有插入到具有优先级的阻塞队列PriorityBlockingQueue的元素必须实现 java.lang.Comparable 接口。因此该队列中元素的排序就取决于你自己的 Comparable 实现。

SynchronousQueue 是一个特殊的队列,它的内部同时只能够容纳单个元素。它更多像是一个汇合点。

阻塞双端队列BlockingDeque 继承自 BlockingQueue。如果生产者线程需要在队列的两端都可以插入数据,消费者线程需要在队列的两端都可以移除数据,这个时候也可以使用BlockingDeque。

链阻塞双端队列LinkedBlockingDeque 类实现了 BlockingDeque 接口。

ConcurrentMap 接口的实现类ConcurrentHashMap和 java.util.HashTable 类很相似,但 ConcurrentHashMap 能够提供比HashTable 更好的并发性能。在你从中读取对象的时候 ConcurrentHashMap 并不会把整个
Map 锁住。此外,在你向其中写入对象的时候,ConcurrentHashMap 也不会锁住整个 Map。它的内部只是把 Map 中正在被写入的部分进行锁定。

并发导航映射ConcurrentNavigableMap是一个支持并发访问的 java.util.NavigableMap,
headMap(T toKey) 方法返回一个包含了小于给定 toKey 的 key 的子 map。
tailMap(T fromKey) 方法返回一个包含了不小于给定 fromKey 的 key 的子 map。
subMap() 方法返回原始 map 中,键介于 from(包含) 和 to (不包含) 之间的子 map。

闭锁 CountDownLatch是一个并发构造,它允许一个或多个线程等待一系列指定操作的完成。CountDownLatch 以一个给定的数量初始化。countDown() 每被调用一次,这一数量就减一。通过调用 await() 方法之一,线程可以阻塞等待这一数量到达零。

栅栏 CyclicBarrier是一个所有线程必须等待的一个栅栏,直到所有线程都到达这里,然后所有线程才可以继续做其他事情。通过调用 CyclicBarrier 对象的 await()方法,两个线程可以实现互相等待。一旦 N 个线程在等待 CyclicBarrier 达成,
所有线程将被释放掉去继续运行。你也可以为等待线程设定一个超时时间。等待超过了超时时间之后,即便还没有达成N 个线程等待 CyclicBarrier 的条件,该线程也会被释放出来。

交换机 Exchanger类表示一种两个线程可以进行互相交换对象的会和点。.exchanger.exchange(xxx);

Semaphore 类是一个计数信号量。这就意味着它具备两个主要方法:acquire(),release()
计数信号量由一个指定数量的 "许可" 初始化。每调用一次 acquire(),一个许可会被调用线程取走。每调用一次 release(),一个许可会被返还给信号量。
信号量主要有两种用途:
1. 保护一个重要(代码)部分防止一次超过 N 个线程进入。
2. 在两个线程之间发送信号。
没有办法保证线程能够公平地可从信号量中获得许可。也就是说,无法担保掉第一个调用
acquire() 的线程会是第一个获得一个许可的线程。如果第一个线程在等待一个许可时发生
阻塞,而第二个线程前来索要一个许可的时候刚好有一个许可被释放出来,那么它就可能会
在第一个线程之前获得许可。
如果你想要强制公平,Semaphore 类有一个具有一个布尔类型的参数的构造子,通过这个参
数以告知 Semaphore 是否要强制公平。强制公平会影响到并发性能,所以除非你确实需要
它否则不要启用它。

执行器服务接口ExecutorService就是一个线程池实现。
既然 ExecutorService 是个接口,如果你想用它的话就得去使用它的实现类之一。
java.util.concurrent 包提供了 ExecutorService 接口的以下实现类:ThreadPoolExecutor,ScheduledThreadPoolExecutor。
但是你也可以使用 Executors 工厂类来
创建 ExecutorService 实例。以下是几个创建 ExecutorService 实例的例子:
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
ExecutorService executorService2 = Executors.newFixedThreadPool(10);
ExecutorService executorService3 = Executors.newScheduledThreadPool(10);
有几种不同的方式来将任务委托给 ExecutorService 去执行:
 execute(Runnable)
 submit(Runnable)
 submit(Callable)
 invokeAny(...)
 invokeAll(...)
要终止 ExecutorService 里的线程你需要调用 ExecutorService 的 shutdown() 方法。
ExecutorService 并不会立即关闭,但它将不再接受新的任务,而且一旦所有线程都完成了
当前任务的时候,ExecutorService 将会关闭。

ThreadPoolExecutor 是 ExecutorService 接 口 的 一 个 实 现 。
ThreadPoolExecutor 包含的线程池能够包含不同数量的线程。池中线程的数量由以下变量决定:corePoolSize,maximumPoolSize
当一个任务委托给线程池时,如果池中线程数量低于 corePoolSize,一个新的线程将被创建,即使池中可能尚有空闲线程。
如果内部任务队列已满,而且有至少 corePoolSize 正在运行,但是运行线程的数量低于maximumPoolSize,一个新的线程将被创建去执行该任务。
但是,除非你确实需要显式为 ThreadPoolExecutor 定 义 所 有 参 数 , 使 用java.util.concurrent.Executors 类中的工厂方法之一会更加方便,正如 ExecutorService 小节所述。

定时执行者服务.ScheduledExecutorService 是一个 ExecutorService,它能够将任务延后执行,或者间隔固定时间多次执行。具有以下实现类:ScheduledThreadPoolExecutor
也可以使用Executors 工厂类来创建一个 ScheduledExecutorService 实例。比如:ScheduledExecutorService scheduledExecutorService =Executors.newScheduledThreadPool(5);
一旦你创建了一个 ScheduledExecutorService,你可以通过调用它的以下方法:
 schedule (Callable task, long delay, TimeUnit timeunit)
 schedule (Runnable task, long delay, TimeUnit timeunit)
 scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)
 scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)

ForkJoinPool 让我们可以很方便地把任务分裂成几个更小的任务,这些分裂出来的任务也将会提交给 ForkJoinPool。任务可以继续分割成更小的子任务,只要它还能分割。
通过把自己分割成多个子任务,每个子任务可以由不同的 CPU 并行执行,或者被同一个CPU 上的不同线程执行。只有当给的任务过大,把它分割成几个子任务才有意义。把任务分割成子任务有一定开销,
因此对于小型任务,这个分割的消耗可能比每个子任务并发执行的消耗还要大。
什么时候把一个任务分割成子任务是有意义的,这个界限也称作一个阀值。这要看每个任务对有意义阀值的决定。很大程度上取决于它要做的工作的种类。
当一个任务将自己分割成若干子任务之后,该任务将进入等待所有子任务的结束之中。一旦子任务执行结束,该任务可以把所有结果合并到同一个结果。
就像提交任务到 ExecutorService 那样,把任务提交到 ForkJoinPool。你可以提交两种类型的任务。一种是没有任何返回值的(一个 "行动"),另一种是有返回值的(一个"任务")。这两种类型分别由 RecursiveAction 和 RecursiveTask 表示。

Lock是一个类似于 synchronized 块的线程同步机制。但是 Lock 比 synchronized 块更加灵活、精细。

Lock 和 synchronized 代码块的主要不同点
synchronized 代码块不能够保证进入访问等待的线程的先后顺序。你不能够传递任何参数给一个 synchronized 代码块的入口。因此,对于 synchronized 
代码块的访问等待设置超时时间是不可能的事情。synchronized 块必须被完整地包含在单个方法里。而一个 Lock 对象可以把它的 lock() 和 unlock() 方法的调用放在不同的方法里。

读写锁 ReadWriteLock允许多个线程在同一时间对某特定资源进行读取,但同一时间内只能有一个线程对其进行写入。
ReadWriteLock 接口有以下实现类:ReentrantReadWriteLock

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.readLock().lock();
readWriteLock.readLock().unlock();
readWriteLock.writeLock().lock();
readWriteLock.writeLock().unlock();

原子性布尔 AtomicBoolean类为我们提供了一个可以用原子方式进行读和写的布尔值,它还拥有一些先进的原子性操作,比如 compareAndSet()。
原子性整型 AtomicInteger
原子性长整型 AtomicLong
原子性引用型 AtomicReference


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值