ConcurrentHashMap使用场景、总结、对比Hashtable、ConcurrentSkipListMap、几种队列;J.U.C线程池、七个参数、四种线程池

ConcurrentHashMap使用场景
ConcurrentHashMap通常只被看做并发效率更高的Map ,用来替换其他线程安全的Map容器,比如Hashtable和Collections.synchronizedMap.线程安全的容器.特别是Map ,很多情况下一个业务中涉及容器的操作有多个,即复合操作,而在并发执行时,线程安全的容器只能保证自身的数据不被破坏,和数据在多个线程间是可见的,但无法保证业务的行为是否正确。

 

ConcurrentHashMap总结:
●HashMap是线程不安全的, ConcurrentHashMap是线程安全的,但是线程安全仅仅指的是对容器操作的时
候是线程安全的
●ConcurrentHashMap的public V get(Object key)不涉及到锁,也就是说获得对象时没有使用锁
●put. remove方法,在jdk7使用锁,但多线程中并不一 定有锁争用,原因在于ConcurrentHashMap将缓存的
变量分到多个Segment ,每个Segment上有一个锁, 只要多个线程访问的不是一个Segment就没有锁争用 ,
就没有堵塞,各线程用各自的锁, ConcurrentHashMap缺省情况下生成16个Segment ,也就是允许16个线程
并发的更新而尽量没有锁争用。而在jdk8中使用的CAS+Synchronized来保证线程安全 ,比加锁的性能更高
●ConcurrentHashMap线程安全的,允许-边更新、一 边遍历 ,也就是说在对象遍历的时候,也可以进行
remove,put操作,且遍历的数据会随着remove,put操作产出变化


对比Hashtable
Hashtable和ConcurrentHashMap的不同点:
●Hashtable对getput,remove都使用了同步操作,它的同步级别是正对Hashtable来进行同步的,也就是说如
果有线程正在遍历集合,其他的线程就暂时不能使用该集合了,这样无疑就很容易对性能和吞吐量造成影响,
从而形成单点。而ConcurrentHashMap则不同 ,它只对put,remove操作使用了同步操作, get操作并不影
响。
●Hashtable在遍历的时候,如果其他线程,包括本线程对Hashtable进行了put , remove等更新操作的话,就
会抛出ConcurrentModificationException异常,但如果使用ConcurrentHashMap的话,就不用考虑这方面
的问题了

Hashtable与HashMap

 

 

了解ConcurrentSkipListMap
        通过对前面ConcurrentHashMap的学习,我们了解到Map存放数据的两种数据结构:链表和红黑树,这两种数据结构各自都有着优缺点。而ConcurrentSkipListMap使用的是第 三种数据结构: SkipList. SkipList有着不低于红黑树的效率。SkipList , 称之为跳表,它是一种可以替代平衡树的数据结构 ,其数据元素默认按照key值升序,天然有序。Skip list让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否 ,通过"空间来换取时间”的一个算法,在每个节点中增加了向前的指针,在插入、删除、查找时可以忽略一些不可能涉及到的结点 ,从而提高了效率。

 

非阻塞队列ConcurrentLinkedQueue
在单线程编程中我们会经常用到一一些集合类 ,比如ArrayList,HashMap等,但是这些类都不是线程安全的类。在面试中也经常会有一些考点 ,比如ArrayList不是线程安全的, Vector是线程安全。而保障Vector线程安全的方式,是非常粗暴的在方法上synchronized独占锁,将多线程执行变成串行化。要想将ArrayList变成线程安全的也可以使用      collections. synchronizedlist(List<T> list) 方法ArrayList转换成线程安全的,但这种转换方式依然是通过synchronized修饰方法实现的,很显然这不是一种高效的方式 ,同时,队列也是我们常用的一种数据结构。
        为了解决线程安全的问题, J.U.C为我们准备了ConcurrentlinkedQueue这个线程安全的队列。从类名就可以看的出来实现队列的数据结构是链式。ConcurrentLinkedQueue是一个基于链接节点的无边界的线程安全队列,遵循队列的FIFO原则,队尾入队,队首出队。采用CAS算法来实现的。
PS:
1. ConcurrentLinkedQueue的.size0)是要遍历一遍集合的,很慢的,所以尽量要避免用size
2.使用了这个ConcurrentLinkedQueue类之后还是需要自己进行同步或加锁操作。例如queue.isEmpty()后再
进行队列操作queue.add()是不能保证安全的,因为可能queue. isEmpty()执行完成后,别的线程开始操作队
列。

 

阻塞队列BlockingQueue
BlockingQueue介绍
BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被
阻塞的情况主要有如下两种:
    1.当队列满了的时候进行入队列操作
    2.当队列空了的时候进行出队列操作
因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操
作;同样,当一个线程试图对一个空队列进行出队列操作时,它将会被阻塞,除非有另一个线程进行了入队列操作。

BlockingQueue对插入操作,移除操作、获取元素操作提供了四种不同的方法用于不同的场景中使用:
1.抛出异常
2.返回特殊值( null或true/false,取决于具体的操作)
3.阻塞等待此操作.直到这个操作成功
4.阻塞等待此操作,直到成功或者超时指定时间。

 

ArrayBlockingQueue
ArrayBlockingQueue是一个由数组实现的有界阻塞队列。 该队列采用FFO的原则对元素进行排序添加的。
ArrayBlockingQueue为有界且固定,其大小在构造时由构造函数来决定,确认之后就不能再改变了。
ArrayBlockingQueue支持对等待的生产者线程和使用者线程进行排序的可选公平策略,但是在默认情况下不
保证线程公平的访问,在构造时可以选择公平策略( fair = true)。公平性通常会降低吞吐量,但是减少了可变性和避免了“不平衡性”。
ArrayBlockingQueue继承AbstractQueue ,实现BlockingQueue接口。java.util.AbstractQueue ,在Queue
接口中扮演着非常重要的作用,该类提供了对queue操作的骨干实现。BlockingQueue继承java.utl.Queue为阻塞队列的核心接口,提供了在多线程环境下的出列、入列操作,作为使用者,则不需要关心队列在什么时候阻塞线程,什么时候唤醒线程,所有一切均由BlockingQueue来完成。
ArrayBlockingQueue内部使用可重入锁ReentrantLock + Condition来完成多线程环境的并发操作。
●ltems,一个定长数组,维护ArrayBlockingQueue的元素
●takeIndex,int ,为ArrayBlockingQueue对首位置
●putIndex,int,ArrayBlockingQueue对尾位置
●count,元素个数
●lock,锁, ArrayBlockingQueue出列入列都必须获取该锁,两个步骤公用一个锁

 

LinkedBlockingQueue
LinkedBlockingQueue和ArrayBlockingQueue的使用方式基本-样,但还是有一定的区别;
1.队列的数据结构不同
ArrayBlockingQueue是一个由数组支持的有界阻塞队列
LinkedBlockingQueue是一个基于链表的有界(可设置)阻塞队列
2.队列中锁的实现不同
ArrayBlockingQueue实现的队列中的锁是没有分离的,即生产和消费用的是同一一个锁;
LinkedBlockingQueue实现的队列中的锁是分离的,即生产用的是putLock ,消费是takeLock
3.在生产或消费时操作不同
ArrayBlockingQueue实现的队列中在生产和消费的时候,是直接将枚举对象插入或移除的; 
LinkedBlockingQueue实现的队列中在生产和消费的时候,需要把枚举对象转换为Node<E进行插入或移除,
会影响性能

 

PriorityBlackingQueue
PriorityBlockingQueue类似于ArrayBlockingQueue内部使用一个独占锁来控制 ,同时只有一个线程可以进行入队和出队。
PriorityBlockingQueue是一个优先级队列 .它在java.util.PriorityQueue的基础上提供了可阻塞的读取操作。它是无界的,就是说向Queue里面增加元素没有数量限制,但可能会导致内存溢出而失败。
PriorityBlockingQueue始终保证出队的元素是优先级最高的元素,并且可以定制优先级的规则,内部使用二叉堆,通过使用一一个二叉树最小堆算法来维护内部数组.这个数组是可扩容的,当当前元素个数>=最大容量时候会通过算法扩容。值得注意的是为了避免在扩容操作时候其他线程不能进行出队操作,实现.上使用了先释放锁,然后通过CAS保证同时只有一个线程可以扩容成功。

小结:
1.优先队列不允许空值,而且不支持non-comparable (不可比较)的对象,比如用户自定义的类。优先队列要
求使用Java Comparable和Comparator接口给对象排序,并且在排序时会按照优先级处理其中的元素。
2.优先队列的头是基于自然排序或者Comparator排序的最小元素。如果有多个对象拥有同样的排序,那么就可
能随机地取其中任意-一个。也可以通过提供的Comparator (比较器)在队列实现自定的排序。当我们获取队列
时,返回队列的头对象。
3、优先队列的大小是不受限制的,但在创建时可以指定初始大小,当我们向优先队列增加元素的时候,队列大小
会自动增加。

 

SynchronousQueue
SynchronousQueue ,实际上它不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同的是,它维护一组线程 ,这些线程在等待看把元素加入或移出队列。SynchronousQueue没有存储功能 ,因此put和take会一直阻塞 ,直到有另一个线程已经准备好参与到交付过程中。

仅当有足够多的消费者,并且总是有一个消费者准备好获取交付的工作时,才适合使用同步队列。这种实现队列的方式看似很奇怪,但由于可以直接交付工作,从而降低了将数据从生产者移动到消费者的延迟。

直接交付方式还会将更多关于任务状态的信息反馈给生产者。当交付被接受时,它就知道消费者已经得到了任务,而不是简单地把任务放入一个队列--这种区别就好比将文件直接交给同事 ,还是将文件放到她的邮箱中并希望她能尽快拿到文件。
SynchronousQueue对于正在等待的生产者和使用者线程而言。默认是非公平排序,也可以选择公平排序策略。但是,使用公平所构造的队列可保证线程以FIFO的顺序进行访问。公平通常会降低吞吐量,但是可以减小可变性并避免得不到服务。


SynchronousQueue特点:
●是一种阻塞队列,其中每个put必须等待一个take ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。
●是线程安全的,是阻塞的。
●不允许使用null元素。
●公平排序策略是指调用put的线程之间,或take的线程之间的线程以FIFO的顺序进行访问。

 

 

 

J.U.C线程池
线程是一个程序员一定会涉及到的概念,但是线程的创建和切换都是代价比较大的。所以,我们需要有一个好的方案能做到线程的复用,这就涉及到一个概念——线程池。 合理的使用线程池能够带来3个很明显的好处:
    1.降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗
    2.提高响应速度:任务到达时不需要等待线程创建就可以立即执行。
    3.提高线程的可管理性:线程池可以统-管理、分配、调优和监控。
java的线程池支持主要通过ThreadPoolExecutor来实现,我们使用的ExecutorService的各种线程池策略都是基于ThreadPoolExecutor实现的.所以ThreadPoolExecutor十分重要。要弄明白各种线程池策略,必须先弄明白ThreadPoolExecutor

 

共有七个参数,每个参数含义如下:
●corePoolSize
线程池中核心线程的数量(也称为线程池的基本大小)。当提交-一个任务时 ,线程池会新建一个线程来执行任务,直到当前线程数等于corePoolSize.如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
●maximumPoolSize
线程池中允许的最大线移数。线程池的阻塞队列满了之后,如果还有任务提交,如果当前的线程数小于maximumPoolSize .则会新建线程来执行任务。注意,如果使用的是无界队列,该参数也就没有什么效果
了。
●keepAliveTime
线程空闲的时间。线程的创建和销毁是需要代价的。线程执行完任务后不会立即销毁,而是继续存活一段时间: keepAliveTime。默认情况下,该参数只有在线程数大于corePoolSize时才会生效。
●unit
keepAliveTime的单位。TimeUnit
●workQueue
用来保存等待执行的任务的BlockQueue阻塞队列,等待的任务必须实现Runnable接口。选择如下: .
    ArrayBlockingQueue :基于数组结构的有界阻塞队列, FIFO.
    LinkedBlockingQueue :基于链表结构的有界阻塞队列, FIFO。
    PriorityBlockingQueue :具有优先级别的阻塞队列。
    SynchronousQueue :不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。
●threadFactory
用于设置创建线程的工厂。ThreadFactory的作用就是提供创建线程的功能的线程工厂 。他是通过
newThread(方法提供创建线程的功能, newThread()方法创建的线程都是"“非守护线程’而且”线程优先级都是默认优先级"。
●handler
RejectedExecutionHandler .线程池的拒绝策略。所谓拒绝策略,是指将任务添加到线程池中时,线程池拒绝该任务所采取的相应策略。当向线程池中提交任务时,如果此时线程池中的线程已经饱和了,而且阻塞队列也已经满了,则线程池会选择一种拒绝策略来处理该任务。
线程池提供了四种拒绝策略:
    AbortPolicy:直接抛出异常,默认策略;
    CallerRunsPolicy :用调用者所在的线程来执行任务;
    DiscardOldestolicy ;丢弃阻塞队列中靠最前的任务,并执行当前任务;
    DiscardPolicy:直接丢弃任务: .
当然我们也可以实现自己的拒绝策略.例如记录日志等等,实现RejectedExecutionHandler接口即可。

 

四种线程池
我们除了可以使用ThreadPoolExecutor自己根据实际情况创建线程池以外, Executor框架也提供了三种线程池,他们都可以通过工具类Executors来创建。
还有一种线程池ScheduledThreadPoolExecutor 。它相当于提供了“延迟”和”周期执行”功能的ThreadPoolExecutor

一、FixedThreadPool
FixedThreadPool是复用固定数量的线程处理一个共享的无边界队列 ,其定义如下:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return. new ThreadPoolExecutor(nThreads, nThreads ,
                        0L,TimeUnit .MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>());
    }

corePoolSize和maximumPoolSize都设置为创建FixedThreadPool时指定的参数nThreads,意味着当线程池满时且阻塞队列也已经满时,如果继续提交任务,则会直接走拒绝策略,该线程池不会再新建线程来执行任务,而是直接走拒绝策略。FixedThreadPool使用的是默认的拒绝策略 ,即AbortPolicy ,则直接抛出异常。

由于该线程池是固定线程数的线程池,当线程池中的线程数量等于corePoolSize时,如果继续提交任务,该任务会被添加到阻塞队列workQueue中.而workQueue使用的是LinkedBlockingQueue ,但没有设置范围,那么则是最大值( Integer.MAX VALUE) , 这基本就相当于个无界队列了。


二、SingleThreadExecutor
SingleThreadExecutor只会使用单个工作线程来执行-一个无边界的队列。
    public static ExecutorService newsingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
                    (new ThreadPoolExecutor(1, 1,
                        eL, TimeUnit . MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>))); 
        }
作为单一worker线程的线程池 ,它把corePool和maximumPoolSize均被设置为1 ,和FixedThreadPool一样使用的是无界队列LinkedBlockingQueue,所以带来的影响和FixedThreadPool一样。
SingleThreadExecutor只会使用单个工作线程,它可以保证认为是按顺序执行的,任何时候都不会有多于一个的任务处于活动状态。注意,如果单个线程在执行过程中因为某些错误中止,新的线程会替代它执行后续线程。


三、CachedThreadPool
CachedThreadPool会根据需要.在线程可用时.重用之前构造好的池中线程,否则创建新线程:
    public static ExecutorService nevCachedThreadPool() {
        return new ThreadPoolExecutor(o, Integer .MAX_ VALUE,
                60L,TimeUnit . SECONDS,
                new SynchronousQueue<Runnable>());
}

它把corePool为0 , maximumPoolSize为Integer.MAX VALUE ,这就意味着所有的任务一提交就会加入到阻塞队列中。因为线程池的基本大小设为0,一般情况 下线程池中没有程池.用的时候再创建。

但是keepAliveTime设置60 , unit设置为秒,意味着空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。阻塞队列采用的SynchronousQueue ,这是是一个没有元素的阻塞队列。

这个线程池在执行大量短生命周期的异步任务时,可以显著提高程序性能。调用execute时,可以重用之前已构造的可用线程,如果不存在可用线程,那么会重新创建一个新的线程并将其加入 到线程池中。如果线程超过60秒还未被使用,就会被中止并从缓存中移除。因此,线程池在长时间空闲后不会消耗任何资源。

但是这样就处理线程池会存在一个问题,如果主线程提交任务的速度远远大于CachedThreadPool的处理速度,则CachedThreadPool会不断地创建新线程来执行任务,这样有可能会导致系统耗尽CPU和内存资源,所以在使用该线程池是, 一定要注意控制并发的任务数.否则创建大量的线程可能导致严重的性能问题。


四、ScheduledThreadPool
Timer与TimerTask虽然可以实现线程的周期和延迟调度,但是Timer与TimerTask存在一些问题:
●Timer在执行定时任务时只会创建一 个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间
隔时间,会发生一些缺陷。
●如果TimerTask抛出RuntimeException , Timer会停止所有任务的运行。
●Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化会出现一些执行上的变化
为了解决这些问题,我们一般都是推荐ScheduledThreadPoolExecutor来实现。

ScheduledThreadPoolExecutor ,继承ThreadPoolExecutor且实现了ScheduledExecutorService接口,它
就相当于提供了“延迟”和“周期执行"功能的ThreadPoolExecutor.

ScheduledThreadPoolExecutor,它可另行安排在给定的延迟后运行命令,或者定期执行命令。需要多个辅
助线程时,或者要求ThreadPoolExecutor具有额外的灵活性或功能时,此类要优于Timer.
 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ConcurrentHashMap适用于多线程环境下对全局变量进行操作的场景。与HashMap相比,ConcurrentHashMap是线程安全的,能够在高并发的情况下保证操作的安全性。它使用CAS和Synchronized机制来实现线程安全,避免了使用锁对整个集合进行同步,提高了效率。ConcurrentHashMap在更新时只会局部锁住某部分数据,而同步读取操作则是完全非阻塞的。因此,当项目中的全局变量需要被多个线程同时操作时,可以考虑使用ConcurrentHashMap来保证线程安全和高效性。 [2] 另外,与Hashtable相比,ConcurrentHashMap具有一些不同之处。Hashtable对get、put和remove操作都使用了同步操作,同步级别是针对Hashtable对象进行同步的。这意味着,如果有一个线程正在遍历Hashtable,其他线程将无法使用该集合,从而对性能和吞吐量产生影响。然而,ConcurrentHashMap只对put和remove操作使用了同步操作,而get操作并不受影响。此外,当使用Hashtable进行遍历时,如果其他线程包括本线程对Hashtable进行了更新操作,会抛出ConcurrentModificationException异常。但如果使用ConcurrentHashMap,就不用考虑这个问题了。因此,当需要在多线程环境下进行集合操作时,可以优先考虑使用ConcurrentHashMap而不是Hashtable。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ConcurrentHashMap的作用与用法](https://blog.csdn.net/qq_37488998/article/details/109715821)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [ConcurrentHashMap使用场景](https://blog.csdn.net/NeilGY/article/details/80430201)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [ConcurrentHashMap使用场景总结、对比Hashtable、ConcurrentSkipListMap几种队列;J.U.C线程池七个...](https://blog.csdn.net/qq_42722241/article/details/108147008)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值