Java教程

Java并发类概述

本文主要是介绍Java并发类概述,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

集合

ConcurrentHashMap

HashMap 1.7 底层使用的数据结构是数组+链表,链表是为了解决hash碰撞的,相同的值插入,链表的插入采用的是头插法。多线程插入会出现循环链表问题。

HashMap 1.8 底层使用的数据结构是数组+链表+红黑树,链表是为了解决hash碰撞的,链表的插入采用的是尾插法,红黑树是解决链表长度过长的查询问题。此处

HashTable ConcurrentHashMap 是解决HashMap 线程不安全的问题,HashTable 是加方法级别的锁,方法上加Synchronized。

HashTable 锁的力度较大,解决HashTable力度较大的问题出现ConcurentHashMap,ConcurrentHashMap 添加Segment的分段锁,力度较小。JDK 8 利用 volatile + CAS 实现无锁化操作,保证线程安全的同时,提高性能。默认容量16,默认加载因子0.75。

ConcurrentSkipListMap

基于跳表实现,按照 key 自然排序,key 不能为 null,类似 TreeMap。

利用 volatile+CAS 来保证线程安全。

ConcurrentSkipListSet

使用 ConcurrentSkipListMap 实现。

CopyOnWriteArrayList

基于数组实现,相当于支持并发的 ArrayList,刚创建时初始化为长度0的数组。

利用写时复制来保证线程安全。

写时复制:数组是 volatile 类型的,修改数组时,首先 ReentrantLock 加锁,然后复制一个副本数组,对副本进行修改完成后,把原来的数组指向这个新的数组完成赋值。读时不用加锁。

CopyOnWriteArraySet

使用 CopyOnWriteArrayList 实现。去重的,但是按照插入顺序排序的。

非阻塞队列

ConcurrentLinkedQueue

基于链表实现的无界的线程安全的非阻塞队列,遵循 FIFO,利用 volatile+CAS 来保证线程安全。

ConcurrentLinkedDeque

基于双向链表实现的无界的线程安全的非阻塞队列,实现方式类似 ConcurrentLinkedQueue。

阻塞队列

实现:通过 ReentrantLock 和 Condition 实现的等待通知模型来实现阻塞队列。

ArrayBlockingQueue

基于数组实现的阻塞队列,需要指定容量。

LinkedBlockingQueue

基于链表实现的阻塞队列,默认容量为 Integer.MAX_VALUE。

实现类似 ArrayBlockingQueue,计数用的原子类 AtomicInteger。

PriorityBlockingQueue

基于二叉小顶堆实现的阻塞队列,保证取出的元素是最小的,默认初始化容量11。

DelayQueue

基于数组实现的延迟阻塞队列。使用时必须实现 Delayed。

原子操作类

利用 volatile+CAS 来保证原子操作。

原子性基本数据类型:AtomicBoolean、AtomicInteger、AtomicLong

原子性对象引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference

原子性数组:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

原子性对象属性更新:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

原子性累加器:DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder

并发工具类

CountDownLatch

功能:指定 N 个线程等待全部完成后,继续执行。

实现:内部类 Sync 实现了 AQS 同步器,初始化时设置 AQS 的同步状态来表示 countDown 的数量,await() 方法把当前线程加入到 AQS 等待队列,让当前线程阻塞住,执行 countDown() 方法会把同步状态减1,当减到0时,唤醒等待队列中的线程。

CyclicBarrier

功能:类似 CountDownLatch,但是支持 reset() 重置状态,能指定到达数量时执行的动作。

实现:基于 ReentrantLock 和 Condition 实现

线程池

Executors类

位于java.util.concurrent包下。

此包中包含所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和Callable 类的工厂和实用方法。此类支持以下各种方法:

  • 创建并返回设置有常用配置字符串的 ExecutorService 的方法。
  • 创建并返回设置有常用配置字符串的 ScheduledExecutorService 的方法。
  • 创建并返回“包装的”ExecutorService 方法,它通过使特定于实现的方法不可访问来禁用重新配置。
  • 创建并返回 ThreadFactory 的方法,它可将新创建的线程设置为已知的状态。
  • 创建并返回非闭包形式的 Callable 的方法,这样可将其用于需要 Callable 的执行方法中。

newFixedThreadPool

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。

在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

newSingleThreadExecutor

创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。

newCachedThreadPool

创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。

对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。

newSingleThreadScheduledExecutor

创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。

(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程会代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newScheduledThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。

newScheduledThreadPool

创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

unconfigurableExecutorService

返回一个将所有已定义的 ExecutorService 方法委托给指定执行程序的对象,但是使用强制转换可能无法访问其他方法。这提供了一种可安全地“冻结”配置并且不允许调整给定具体实现的方法。

defaultThreadFactory()

返回用于创建新线程的默认线程工厂。

此工厂创建同一 ThreadGroup 中 Executor 使用的所有新线程。如果有 SecurityManager,则它使用System.getSecurityManager() 组来调用此 defaultThreadFactory 方法,其他情况则使用线程组。每个新线程都作为非守护程序而创建,并且具有设置为Thread.NORM_PRIORITY 中较小者的优先级以及线程组中允许的最大优先级。新线程具有可通过 pool-N-thread-M 的Thread.getName() 来访问的名称,其中 N 是此工厂的序列号,M 是此工厂所创建线程的序列号。

privilegedThreadFactory()

返回用于创建新线程的线程工厂,这些新线程与当前线程具有相同的权限。

此工厂创建具有与 defaultThreadFactory() 相同设置的线程,新线程的 AccessControlContext 和 contextClassLoader 的其他设置与调用此privilegedThreadFactory 方法的线程相同。可以在 AccessController.doPrivileged(java.security.PrivilegedAction) 操作中创建一个新privilegedThreadFactory,设置当前线程的访问控制上下文,以便创建具有该操作中保持的所选权限的线程。

注意,虽然运行在此类线程中的任务具有与当前线程相同的访问控制和类加载器,但是它们无需具有相同的 ThreadLocal 或 InheritableThreadLocal 值。如有必要,使用 ThreadPoolExecutor.beforeExecute(java.lang.Thread, java.lang.Runnable) 在ThreadPoolExecutor 子类中运行任何任务前,可以设置或重置线程局部变量的特定值。另外,如果必须初始化 worker 线程,以具有与某些其他指定线程相同的 InheritableThreadLocal 设置,则可以在线程等待和服务创建请求的环境中创建自定义的 ThreadFactory,而不是继承其值。

ThreadPoolExecutor

核心参数说明

1、corePoolSize:核心线程数
    * 核心线程会一直存活,及时没有任务需要执行
    * 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
    * 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭

2、queueCapacity:任务队列容量(阻塞队列)
    * 当核心线程数达到最大时,新任务会放在队列中排队等待执行

3、maxPoolSize:最大线程数
    * 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
    * 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常

4、 keepAliveTime:线程空闲时间
    * 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
    * 如果allowCoreThreadTimeout=true,则会直到线程数量=0

5、allowCoreThreadTimeout:允许核心线程超时
6、rejectedExecutionHandler:任务拒绝处理器
    * 两种情况会拒绝处理任务:
        - 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
        - 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
    * 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
    * ThreadPoolExecutor类有几个内部实现类来处理这类情况:
        - AbortPolicy 丢弃任务,抛运行时异常
        - CallerRunsPolicy 执行任务
        - DiscardPolicy 忽视,什么都不会发生
        - DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
    * 实现RejectedExecutionHandler接口,可自定义处理器

执行顺序说明

线程池按以下行为执行任务
    1. 当线程数小于核心线程数时,创建线程。
    2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
    3. 当线程数大于等于核心线程数,且任务队列已满
        -1 若线程数小于最大线程数,创建线程
        -2 若线程数等于最大线程数,抛出异常,拒绝任务

如何设置参数

1、默认值
    * corePoolSize=1
    * queueCapacity=Integer.MAX_VALUE
    * maxPoolSize=Integer.MAX_VALUE
    * keepAliveTime=60s
    * allowCoreThreadTimeout=false
    * rejectedExecutionHandler=AbortPolicy()

2、如何来设置
    * 需要根据几个值来决定
        - tasks :每秒的任务数,假设为1000
        - taskcost:每个任务花费时间,假设为0.1s
        - responsetime:系统允许容忍的最大响应时间,假设为1s
    * 做几个计算
        - corePoolSize = 每秒需要多少个线程处理? 
            * 一颗CPU核心同一时刻只能执行一个线程,然后操作系统切换上下文,核心开始执行另一个线程的代码,以此类推,超过cpu核心数,就会放入队列,如果队列也满了,就另起一个新的线程执行,所有推荐:corePoolSize = ((cpu核心数 * 2) + 有效磁盘数),java可以使用Runtime.getRuntime().availableProcessors()获取cpu核心数
        - queueCapacity = (coreSizePool/taskcost)*responsetime
            * 计算可得 queueCapacity = corePoolSize/0.1*1。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
            * 切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
        - maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
            * 计算可得 maxPoolSize = (1000-corePoolSize)/10,即(每秒并发数-corePoolSize大小) / 10
            * (最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数
        - rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
        - keepAliveTime和allowCoreThreadTimeout采用默认通常能满足

 

这篇关于Java并发类概述的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!