热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【并发编程】5.原子类与并发容器

一、原子类1.什么是原子类Java的java.util.concurrent包除了提供底层锁、并发集合外,还提供了一组原子操作的封装类,它们位于java.util.concurre

一、原子类


1.什么是原子类

Java的java.util.concurrent包除了提供底层锁、并发集合外,还提供了一组原子操作的封装类,它们位于java.util.concurrent.atomic包。

Atomic类是通过无锁(lock-free)的方式实现的线程安全(thread-safe)访问


2.原子类是基于什么实现的

原子类的实现是基于CPU本身提供的 CAS指令,是一种无锁的实现;

CAS Compare and Swap 比较并交换,即在更新之前会校验是否于期待的值相等

执行函数:CAS(V,E,N)

V表示要更新的变量;E表示预期值;N表示新值

CAS 容易出现ABA问题

容易出现在原子变量值不确定容易反复的情况

假设 count 原本是 A,

线程1检查数值 期望值为 A,未执行更新操作 ,

此时 线程2 更新值为B,

线程3更新值 为 A,

然后 线程1执行更新操作 ,期望值与实际值相同执行操作。

解决方案:类似乐观锁,加上版本号。


3.常用的原子类

1. 原子化的基本数据类型

相关实现有 AtomicBoolean、AtomicInteger 和 AtomicLong

2. 原子化的对象引用类型

相关实现有 AtomicReference、AtomicStampedReference 和 AtomicMarkableReference,利用 它们可以实现对象引用的原子化更新。

ABA 问题

AtomicStampedReference 和 AtomicMarkableReference 这两个原子类可以解决 ABA 问题

3. 原子化数组

相关实现有 AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray,利用这些原子 类,我们可以原子化地更新数组里面的每一个元素。这些类提供的方法和原子化的基本数据类型 的区别仅仅是:每个方法多了一个数组的索引参数,所以这里也不再赘述了。

4. 原子化对象属性更新器

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

5. 原子化的累加器

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


4.原子类的用途

适用于计数器,累加器等场景(感觉redis更好用..)


二、并发容器


1.同步容器

java 1.5之前在java.util包中提供了Vector和HashTable两个同步容器

是对所有的方法都加上了同步关键字synchronized,确保一个实例只有一个线程能操作。

这样所有的操作就都是串行的,性能较差。


2.常用的并发容器与实现原理


2.1 CopyOnWriteArrayList 是唯一的并发List 读操作完全无锁

/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock(); //可重入锁
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array; //内部维护的对象数组
public E get(int index) {
return get(getArray(), index);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}


//进行写操作时会把当前数组copy一份,进行写操作后将新的数组赋值给 array
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

CopyOnWriteArrayList 迭代器是只读的,不支持增删改。因为迭代器遍历的仅仅是一个快照,而对快照进行增删改是没 有意义的。


2.2.ConcurrentHashMap(key 无序)和 ConcurrentSkipListMap(key 有序)

ConcurrentHashMap与ConcurrentSkipListMap中key value 都不能为空,否则会空指针

ConcurrentHashMap容器相较于CopyOnWrite容器在并发加锁粒度上有了更大一步的优化,它通过修改对单个hash桶元素加锁的达到了更细粒度的并发控制。



  • 在发生hash冲突时仅仅只锁住当前需要添加节点的头元素即可,可能是链表头节点或者红黑树的根节点,其他桶节点都不需要加锁,大大减小了锁粒度。

  • ConcurrentHashMap容器是通过CAS + synchronized一起来实现并发控制的。

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode()); //计算key的hash值
int binCount = 0;
//循环插入元素,避免并发插入失败
for (Node[] tab = table;;) {
Node f; int n, i, fh;//f是hash桶的头结点
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//如果当前hash桶无元素,通过CAS操作插入新节点
if (casTabAt(tab, i, null,
new Node(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//不断循环计算table(散列表)的每个桶位(slot)的散列值i ,直到找到tab[i] 为空的桶位,casTabAt将put(增加)的节点Node 放到空仓(empty bin)中,如果在put 的过程中,别的线程更改了tab[i],导致tab[i] 不为空,那么casTabAt返回false,继续循环找tab[i]== null的桶位。
//如果当前桶正在扩容,则协助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//hash冲突时锁住当前需要添加节点的头元素,可能是链表头节点或者红黑树的根节点
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node pred = e;
if ((e = e.next) == null) {
pred.next = new Node(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node p;
binCount = 2;
if ((p = ((TreeBin)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}

2.3 Set

Set 接口的两个实现是

CopyOnWriteArraySet 参考CopyOnWriteArrayList

ConcurrentSkipListSe 参考ConcurrentSkipListMap


2.4 Queue

阻塞与非阻塞,所谓阻塞指的是当队列已满时,入队操作阻塞;当队列已空时,出队操作阻塞。

单端与双端,单端指的是只能队尾入队,队首出队;而双端指的是队首队尾皆可入 队出队。

阻塞队列都用 Blocking 关键字标识,单端队列使用 Queue 标识,双端队 列使用 Deque 标识。

1.单端阻塞队列



  • ArrayBlockingQueue、

  • LinkedBlockingQueue、

  • SynchronousQueue、

  • LinkedTransferQueue、

  • PriorityBlockingQueue、

  • DelayQueue。

内部一般会持有一个队列,这个队列可以是数组(其实现是 ArrayBlockingQueue)也可以是链表(其实 现是 LinkedBlockingQueue);甚至还可以不持有队列(其实现是 SynchronousQueue),

LinkedTransferQueue融合了LinkedBlockingQueue、 SynchronousQueue功能 性能更好

PriorityBlockingQueue 支持按优先级出队

DelayQueue 支持延时出队

2.双端阻塞队列

其实现是 LinkedBlockingDeque。

3.单端非阻塞队列

其实现是 ConcurrentLinkedQueue。

4.双端非阻塞队列

其实现是 ConcurrentLinkedDeque。

阻塞队列相关API方法的区别



推荐阅读
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 关于CMS收集器的知识介绍和优缺点分析
    本文介绍了CMS收集器的概念、运行过程和优缺点,并解释了垃圾回收器的作用和实践。CMS收集器是一种基于标记-清除算法的垃圾回收器,适用于互联网站和B/S系统等对响应速度和停顿时间有较高要求的应用。同时,还提供了其他垃圾回收器的参考资料。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了Java的公式汇总及相关知识,包括定义变量的语法格式、类型转换公式、三元表达式、定义新的实例的格式、引用类型的方法以及数组静态初始化等内容。希望对读者有一定的参考价值。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 测绘程序设计Excel度分秒转换模板附代码超实用版
    本文介绍了测绘程序设计Excel度分秒转换模板附代码超实用版的相关知识,包括准备工作、编写表达式和注意事项。在实际工作中,将GPS实测的经纬度度转换为度分秒是常见需求,本文提供了在Excel中快速进行转换的方法,以提高工作效率。 ... [详细]
  • 处理docker容器时间和宿主机时间不一致问题的方法
    本文介绍了处理docker容器时间和宿主机时间不一致问题的方法,包括复制主机的localtime到容器、处理报错情况以及重启容器的步骤。通过这些方法,可以解决docker容器时间和宿主机时间不一致的问题。 ... [详细]
author-avatar
wsx迪_257
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有