引言
在Java标准库中,大部分集合类都是线程不安全的。Vector(比ArrayList多了同步化机制就变得线程安全了);Stack(继承Vector);Hashtable(只比Hashmap多了线程安全);以Concurrent开头的集合类:ConcurrentHashMap、ConcurrentLinkedQueue等;以CopyOnWrite开头的集合类:CopyOnWriteArrayList、CopyOnWriteArraySet等。以上这些都是线程安全的,其他的则不是线程安全。接下来分别介绍这些类的用法。队列
多线程下使用ArrayList
1. 手动加锁。使用synchronized、ReentrantLock。比如在一些方法上加锁等等。
2. 自动加锁。使用Collections.synchronizedList(new ArrayList<>())
Collections工具类里提供的这个工具相当于手动加锁的效果。
3. 使用CopyOnWrite。使用COW(写时拷贝),CopyOnWriteArrayList保证线程安全。
不同于上述两种方案,这种方法是如果有写操作,就把原来的集合先复制一份,在这个复制的集合上操作。操作完成后再把原来集合的引用指向刚才复制的。(以上操作是原子的)
优点:在读多写少的场景下,没有了锁竞争,效率更高
缺点:如果数据量很大,那么复制也是要消耗很多资源的。
新写的数据不能第一时间读取到。
应用场景:这种读写分离的思想在服务器程序配置的维护上。
程序有很多的功能,这些功能开关我们可以放到配置文件上。如果不想要其中一个功能,那么可以在配置文件上关闭这个开关。一般配置文件修改完成后需要重启服务器才能生效,但是重启服务器会带来很大的损失。所以就会有”热加载“这个方法来解决。复制一份相同的配置文件,然后修改成目标文件;与此同时还是先用原来旧的文件,等修改完成后再使用新的即可。
多线程下使用哈希表
HashMap是线程不安全的,所以不考虑使用。
Hashtable是线程安全的,但是效率比较低,也不考虑使用。
效率低的原因:
1. 如果多个线程同时使用Hashtable,就直接造成锁冲突了。如下图。
2. size属性也是通过synchronized来控制的,这是比较慢的。
3. 扩容的时候,是一次性全部调整完,把全部的元素拷贝到新的数组上的链表/红黑树上,那这次 扩容效率就极低。
ConcurrentedHashMap
线程安全的,效率也比较高。所以通常情况下我们使用这个类。接下来就解释以下为什么效率高。
效率高的原因(解决了Hashtable效率低的问题):
1. (JDK1.8情况下)给每个头节点都分别加上不同的锁。如下图所示:
而在JDK1.7及以前,则使用的是分段锁。如下图:
2. 使用了更多的CAS操作,比如size这个属性,避免出现了重量级锁。
3. 优化扩容方式。
当放不下之后(超出负载因子设定值),就要进行扩容。
①先创作出来一个数组,把一部分的数据移过去(插入新值,删除旧值)。
②如果再次put时,在把一部分数据移过去。
③直到数据全部移除完成后,删除旧数组。
④在此期间如果要查询,两个数组的数据都要查;如果要删除,查询后找到删除即可。
4. 只对写操作加锁
读与读之间没有冲突;
读与写之间也没有冲突;(通过volatile和原子的写操作来精确控制不会发生脏读的情况)
写与写之间有冲突。
有什么错误评论区指出。希望可以帮到你。