中学生做网站,郑州怎样建设公司网站,网络平台推广公司,东莞网站建设品牌目录
前言
一、多线程环境使用 ArrayList
1.进行加锁
2.使用 SynchronizedList 类
3.使用 CopyOnWriteArrayList 类
二、多线程环境使用队列
1.进行加锁
2.使用阻塞队列
三、多线程环境使用哈希表
1.Hashtable
2.ConcurrentHashMap
#xff08;1#xff09;缩小锁…目录
·前言
一、多线程环境使用 ArrayList
1.进行加锁
2.使用 SynchronizedList 类
3.使用 CopyOnWriteArrayList 类
二、多线程环境使用队列
1.进行加锁
2.使用阻塞队列
三、多线程环境使用哈希表
1.Hashtable
2.ConcurrentHashMap
1缩小锁粒度
2使用 CAS 机制
3优化扩容操作
3.Hashtable、HashMap 与 ConcurrentHashMap 的区别
·结尾 ·前言 介绍了这么多关于多线程相关的知识后在我们 Java 中哪些集合类在使用的时候是线程安全的呢其实我们了解的集合类大部分都是线程不安全的像 HashMap、ArrayList……那么本篇文章就来介绍一下哪些集合类是线程安全的它们又是如何保证线程安全的。
一、多线程环境使用 ArrayList
1.进行加锁 为了保证在多线程环境下对 ArrayList 进行操作是线程安全的我们可以在调用 ArrayList 的相关方法时都进行加锁操作使用 synchronized 或者使用 ReentrantLock这样就可以保证线程的安全。
2.使用 SynchronizedList 类 使用这种方式创建 ArrayList 方式如下代码所示
List list Collections.synchronizedList(new ArrayList()); 这种方式可以认为是给 ArrayList 套了一个壳原本 ArrayList 各种操作都是不带锁的通过上述套壳操作后得到了新的对象在新的对象中里面的方法就都是带有锁的。 使用这种方式可以把 List 所以的子类都转成线程安全的类关于这种方式的详细用法与介绍可以参考一下Collections.synchronizedList使用 - hongdada - 博客园 这篇文章里面的讲解十分详细我在这里就不展开进行介绍啦。
3.使用 CopyOnWriteArrayList 类 CopyOnWriteArrayList 类保证线程安全的做法是用到了写时拷贝在多线程环境下使用 ArrayList 遇见的线程安全问题大多都是涉及到多个线程对同一个 ArrayList 进行修改操作使用 CopyOnWriteArrayList 在面对多个线程修改时进行的操作就是把整个顺序表复制一份修改新的顺序表中的内容修改完成后再修改引用的指向使引用指向新的顺序表用这种方式来保证线程安全这个过程如下图所示 最后修改引用指向的操作是原子的所以不加锁也不会有问题。 上述的这种操作也是存在一定的局限性的比如出现一下两种情况 对 List 涉及到频繁的修改操作顺序表非常大 此时使用 CopyOnWriteArrayList 写时拷贝就会产生很大的开销如果这种开销比加锁的开销还要大那就得不偿失了。
二、多线程环境使用队列
1.进行加锁 想要在多线程环境中使用队列保证线程安全那么我们可以在调用队列相关方法时都进行加锁的操作使用 synchronized 或者使用 ReentrantLock这样就可以保证线程的安全。
2.使用阻塞队列 使用 BlockingQueue 实现的阻塞队列要注意阻塞队列不仅有阻塞的作用还有保证线程安全的作用关于阻塞队列的介绍可以参考我前面的文章多线程——阻塞队列_php 队列阻塞问题-CSDN博客 这里我对阻塞队列进行了详细的介绍已经实现一个简单的阻塞队列。
三、多线程环境使用哈希表 在多线程中使用 HashMap 本身不是线程安全的想要在多线程环境下使用哈希表就可以使用Hashtable 或者 ConcurrentHashMap。
1.Hashtable Hashtable 这里保证线程安全的方式很简单如下图所示 进入 Hashtable 源码可以发现Hashtable 这里保证线程安全的方式就是把关键的方法都加上 synchronized 关键字前面介绍过对方法加 synchronized 进行修饰等同于直接针对 Hashtable 对象本身进行加锁这时就会出现下图的情况 上图两个线程是想针对不同的链表进行修改操作可是由于 Hashtable 中 synchronized 直接对 Hashtable 对象本身进行了加锁所以即使是修改不同的链表也会出现锁冲突。 我们可以仔细观察一下上图如果是修改两个不同链表上的元素就不会涉及到线程安全的问题这属于修改不同变量但是如果是修改同一个链表上的元素就可能涉及到线程安全问题此时针对不同链表的操作再进行加锁就产生了多余的锁冲突了。 下面我来总结一下 Hashtable 的缺点有以下几条 如果多个线程访问同一个 Hashtable 就会直接发送锁冲突这时就会产生一些多余的锁冲突造成代码在多线程执行中效率低下size 方法也是使用 synchronized 进行修饰的操作起来就会比较慢Hashtable 一旦涉及到扩容操作就由该线程完成整个扩容操作这个过程中涉及到大量的元素拷贝效率会非常低。 2.ConcurrentHashMap ConcurrentHashMap 相比与 Hashtable 做出了一系列的改进和优化下面我来介绍一下ConcurrentHashMap 都做了些什么优化。
1缩小锁粒度 为了优化 Hashtable 中对不同链表操作出现的多余锁冲突在 ConcurrentHashMap 中的加锁方式是对每条链表都分配一个单独的锁如下图所示 此时t1 线程与 t2 线程在修改不同链表的时候就不会出现锁冲突并且这里虽然是对每条链表都分配了把锁但是并没有产生更多的空间代价这是因为 Java 中任何一个对象都可以作为锁对象在哈希表中本身就要有数组数组中每个元素都是已经存在的每个链表的头节点此时只要使用数组元素链表头节点作为加锁的对象即可完成对每条链表都分配锁的操作。 在 Java 1.7 及以前ConcurrentHashMap 是通过“分段锁”来实现的就是给若干个链表分配一把锁这种设定实现更复杂效率也不高还会引入额外的空间开销所以从 Java 1.8 开始ConcurrentHashMap 就设定成每个链表一把锁了。
2使用 CAS 机制 在 ConcurrentHashMap 中充分使用了 CAS 机制进行原子操作减少了部分加锁比如在针对整个哈希表元素的个数维护。
3优化扩容操作 扩容对于哈希表来说永远都是一个重量级的操作在 HashMap 中有一个负载因子它用来描述每个链表上平均有多少元素为了保证哈希表的查找效率每个链表上的元素个数不应该太长如果太长就会涉及到以下两种解决方案 面对个别链表长度过长会将该链表变成树状结构如果负载因子到达规定阈值就会进行扩容操作。 所谓的扩容操作就是创建一个更大的数组把旧的哈希表中的元素都搬运插入/删除到新的数组上如果哈希表本身元素有很多这里的扩容操作就会消耗很长时间。 那么 ConcurrentHashMap 是如何针对扩容操作进行的优化呢这里使用的是化整为零的思路以往的 HashMap 或者 Hashtable 面对扩容操作都是在某一次插入元素操作中将整体一次性完成扩容而 ConcurrentHashMap 在面对扩容操作时是每次操作都只搬运一部分元素这样确保每次操作消耗的时间都不会很长就避免了出现很卡的情况了。 在 ConcurrentHashMap 进行扩容的过程中会同时存两份哈希表一份是旧的一份是新的此时进行的各操作流程为 插入操作直接往新的哈希表中插删除操作新的哈希表与旧的哈希表中都直接删除查找操作在新的哈希表与旧的哈希表中都进行查询。 最后扩容完成后把旧的哈希表给删除。
3.Hashtable、HashMap 与 ConcurrentHashMap 的区别 HashMap线程不安全key 允许为 nullHashtable线程安全使用 synchronized 锁 Hashtable 对象增加多余的所冲突效率较低key 不允许为 nullConcurrentHashMap线程安全使用 synchronized 锁每个链表的头节点锁冲突的概率降低充分利用 CAS 机制优化了扩容的方式key 不允许为 null。 ·结尾 文章到此就要结束了本篇文章介绍了一些线程安全的集合类并且介绍了它们是如何保证线程安全的希望看完本篇文章让你在多线程编程的过程中会多注意使用的类是否存在线程安全问题并且理解上述线程安全的集合类为保证线程安全所进行操作的思路这会对我们在多线程环境下如何使用线程不安全的类也能写出线程安全的代码有所帮助如果本篇文章对你有所帮助希望能得到你的支持如果对文章有什么不理解的地方欢迎在评论区进行留言那么我们就下一篇文章再见咯~~~