java并发编程之五、工具类

java在线程同步和互斥方面在语言和工具方面都提供了相应的支撑,与此同时,java还提供了一系列的并发容器和原子类,来使得并发编程更容易。

一。并发容器

(一)。同步容器

同步容器指的是容器本身使用synchronized关键字来同步访问,包括我们都知道的HashTable,也包括Vector和Stack。另外,也可以通过工具类Collections.synchronizedList(List<T> list)这个方法将线程不安全的ArrayList转成线程安全的包装类,其他的set,map等等,都有类似的包装类。

通常都任务同步容器的性能较差,但不足以导致问题,会导致问题的是对同步容易的迭代遍历。在迭代遍历的时候,依然需要对同步容器本身进行加锁才能保证线程安全。

1 List list = Collections.synchronizedList(new ArrayList());
2 synchronized (list) {
3  Iterator i = list.iterator();
4 while (i.hasNext())
5  foo(i.next());
6 }

(二)。并发容器

同步容器的线程安全的保证主要是通过对所有的访问路径添加synchronized保护,往往造成性能不佳,所以在java 1.5之后,提供了更多的性能比较优秀的容器,这里称之为并发容器。

根据容器的类型,大致可以分为以上四类。

A。List

CopyOnWriteArrayList 主要体现了读写分离和最终一致的原则。即每次对CopyOnWriteArrayList 对象进行更改的话,都会新创建一个内部对象,而之前已经开始的读,还是基于之前的内部对象,之后的读在基于新的对象,会造成短暂的不一致。

如果要使用CopyOnWriteArrayList,务必保证这个业务场景中读的频率是写的频率的千百倍以上,否则效率比较差,其次就是能够接受短暂的不一致现象。

B。Map

Map 接口的两个实现是 ConcurrentHashMap 和 ConcurrentSkipListMap,它们从应用的角度来看,主要区别在于 ConcurrentHashMap 的 key 是无序的,而 ConcurrentSkipListMap 的 key 是有序的。

使用 ConcurrentHashMap 和 ConcurrentSkipListMap 需要注意的地方是,它们的 key 和 value 都不能为空,否则会抛出NullPointerException这个运行时异常。

ConcurrentSkipListMap 的插入、删除、查询操作平均的时间复杂度是 O(log n),理论上和并发线程数没有关系,所以在并发度非常高的情况下,若你对 ConcurrentHashMap 的性能不满意,可以尝试一下 ConcurrentSkipListMap。

C。Set

Set 接口的两个实现是 CopyOnWriteArraySet 和 ConcurrentSkipListSet,使用场景可以参考前面讲述的 CopyOnWriteArrayList 和 ConcurrentSkipListMap。

D。Queue

Queue 并发容器可以从以下两个维度来分类:

* 一个维度是阻塞与非阻塞,所谓阻塞指的是当队列已满时,入队操作阻塞;当队列已空时,出队操作阻塞。Java 并发包里阻塞队列都用 Blocking 关键字标识。

* 一个维度是单端与双端,单端指的是只能队尾入队,队首出队;而双端指的是队首队尾皆可入队出队。单端队列使用 Queue 标识,双端队列使用 Deque 标识。

a.单端阻塞队列,ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、LinkedTransferQueue、PriorityBlockingQueue 和 DelayQueue。LinkedTransferQueue 融合 LinkedBlockingQueue 和 SynchronousQueue 的功能,性能比 LinkedBlockingQueue 更好;PriorityBlockingQueue 支持按照优先级出队;DelayQueue 支持延时出队。

b.双端阻塞队列,LinkedBlockingDeque。

c.单端非阻塞队列,ConcurrentLinkedQueue。

d.双端非阻塞队列,ConcurrentLinkedDeque。

使用队列时,需要格外注意队列是否支持有界(所谓有界指的是内部的队列是否有容量限制)。实际工作中,一般都不建议使用无界的队列,因为数据量大了之后很容易导致 OOM。上面提到的这些 Queue 中,只有 ArrayBlockingQueue 和 LinkedBlockingQueue 是支持有界的,所以在使用其他无界队列时,一定要充分考虑是否存在导致 OOM 的隐患。

(三)。性能比较

下图对比了都具有线程安全特性的List的三个实现的性能,可以看出CopyOnWriteArrayList在遍历操作的时候性能最好,在读操作上与其他两个相差无几,而写操作非常差。

二。原子类

对于简单的原子性问题,还有一种无锁方案。Java SDK 并发包将这种无锁方案封装提炼之后,实现了一系列的原子类。无锁方案相对互斥锁方案,最大的好处就是性能。互斥锁方案为了保证互斥性,需要执行加锁、解锁操作,而加锁、解锁操作本身就消耗性能;同时拿不到锁的线程还会进入阻塞状态,进而触发线程切换,线程切换对性能的消耗也很大。 相比之下,无锁方案则完全没有加锁、解锁的性能消耗,同时还能保证互斥性,既解决了问题,又没有带来新的问题,可谓绝佳方案。

原子类性能高的秘密很简单,硬件支持而已。CPU 为了解决并发问题,提供了 CAS 指令(CAS,全称是 Compare And Swap,即“比较并交换”)。CAS 指令包含 3 个参数:共享变量的内存地址 A、用于比较的值 B 和共享变量的新值 C;并且只有当内存中地址 A 处的值等于 B 时,才能将内存中地址 A 处的值更新为新值 C。作为一条 CPU 指令,CAS 指令本身是能够保证原子性的。

Java SDK 并发包里的原子类很丰富,可以将它们分为五个类别:原子化的基本数据类型、原子化的对象引用类型、原子化数组、原子化对象属性更新器和原子化的累加器。

(一)原子化的基本数据类型,相关实现有 AtomicBoolean、AtomicInteger 和 AtomicLong。

(二)原子化的对象引用类型,相关实现有 AtomicReference、AtomicStampedReference 和 AtomicMarkableReference,利用它们可以实现对象引用的原子化更新。AtomicStampedReference 和 AtomicMarkableReference 这两个原子类可以解决 ABA 问题。

(三)原子化数组,相关实现有 AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray,利用这些原子类,可以原子化地更新数组里面的每一个元素。

(四)原子化对象属性更新器,相关实现有 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater,利用它们可以原子化地更新对象的属性,这三个方法都是利用反射机制实现的。

(五)原子化的累加器,DoubleAccumulator、DoubleAdder、LongAccumulator 和 LongAdder,这四个类仅仅用来执行累加操作,相比原子化的基本数据类型,速度更快,但是不支持 compareAndSet() 方法。

无锁方案相对于互斥锁方案,优点非常多,首先性能好,其次是基本不会出现死锁问题(但可能出现饥饿和活锁问题,因为自旋会反复重试)。所有原子类的方法都是针对一个共享变量的,如果你需要解决多个变量的原子性问题,建议还是使用互斥锁方案。

原文地址:https://www.cnblogs.com/029zz010buct/p/12182098.html

时间: 2024-07-29 18:14:12

java并发编程之五、工具类的相关文章

【转】Java并发编程:Thread类的使用

Java并发编程:Thread类的使用 Java并发编程:Thread类的使用 在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学习一下Thread类,在学习Thread类之前,先介绍与线程相关知识:线程的几种状态.上下文切换,然后接着介绍Thread类中的方法的具体使用. 以下是本文的目录大纲: 一.线程的状态 二.上下文切换 三.Thread类中的方法 若有不正之处,请多多谅解并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http:/

【JAVA并发】同步工具类

同步工具类主要包括闭锁(如CountDownLatch),栅栏(如CyclicBarrier),信号量(如Semaphore)和阻塞队列(如LinkedBlockingQueue)等: 使用同步工具类可以协调线程的控制流: 同步工具类封装了一些状态,这些状态决定线程是继续执行还是等待,此外同步工具类还提供了修改状态的方法: 下面将简单介绍以上同步工具类: 闭锁 可以让一个线程等待一组事件发生后(不一定要线程结束)继续执行: 以CountDownLatch为例,内部包含一个计数器,一开始初始化为一

Java并发编程:Thread类的使用

一.线程的状态 在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解. 线程从创建到最终的消亡,要经历若干个状态.一般来说,线程包括以下这几个状态:创建(new).就绪(runnable).运行(running).阻塞(blocked).time waiting.waiting.消亡(dead). public enum State { /** * Thread state for a thread which has not

Java并发编程:Thread类的使用介绍

在学习Thread类之前,先介绍与线程相关知识:线程的几种状态.上下文切换,然后接着介绍Thread类中的方法的具体使用. 以下是本文的目录大纲: 一.线程的状态 二.上下文切换 三.Thread类中的方法 若有不正之处,请多多谅解并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/dolphin0520/p/3920357.html 一.线程的状态 在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面

并发编程常用工具类之countDownLatch和cyclicBarrier的使用对比

1.CountDownLatch           countDownLatch的作用是让一组线程等待其他线程完成工作以后在执行,相当于加强版的join(不懂可以百度一下join的用法),一般在初始化的时候会在构造方法传入计数器, 后续,在其他线程中每次调用countDown方法计数器减一,一般在需要等待的线程中调用countDownLatch的await方法阻塞线程,在当计数器为0时,等待线程继续运行. 光看上面的定义描述不是很直观,我们再来结合代码看一下实际运用: 1 public cla

并发编程常用工具类(二) SymaPhore实现线程池

1.symaPhore简介 symaphore(信号量)用来控制同时访问某个资源的线程数量,一般用在并发流量控制.个人对它的理解相当于是接待室每次只能接待固定数量的人,当达到最高接待数的时候,其他人就会被拦截在外等待,当前面接待完走出接待室,才会继续接待下面的人. 2.symaphore使用 symaphore有两个构造方法:构造方法Semaphore(int permits)接受一个int参数,表示可用的许可证数量,内部默认创建一个非公平锁:构造方法Semaphore(int permits,

Java并发编程-总纲

Java 原生支持并发,基本的底层同步包括:synchronized,用来标示一个方法(普通,静态)或者一个块需要同步执行(某一时刻,只允许一个线程在执行代码块).volatile,用来标识一个变量是共享变量(线程不缓存),更新和读取是原子的.wait,线程等待某一个Object上的事件(notify事件,线程挂起,释放锁),需要在synchronized区中执行.notify,事件发生后,通知事件,通知一个挂起的线程,需要在synchronized区中执行.notifyAll,事件发生后,通知

Java并发编程、多线程、线程池…

Java多线程干货系列(1):Java多线程基础http://www.importnew.com/21136.html#comment-651146 40个Java多线程问题总结http://www.importnew.com/18459.html#comment-651217 Java线程面试题 Top 50http://www.importnew.com/12773.html Java并发编程:Thread类的使用http://www.cnblogs.com/dolphin0520/p/39

java并发编程[持续更新]

[toc] java并发编程 1.常用类介绍 Semaphore Semaphore 类是一个计数信号量,必须由获取它的线程释放, 通常用于限制可以访问某些资源(物理或逻辑的)线程数目. Semaphore包含三种操作 初始化 获取acquire() 释放 release() 当信号量大于0的时候semaphore会响应线程请求,释放资源,当信号量等于0时即阻塞线程. Semaphore有两种模式,公平模式和非公平模式 公平模式遵循FIFO,按照acquire的顺序来分配资源,非公平则为抢占式的