目录
🍂区别
🍃扩容方式
常用的ArrayList,LinkedList,HashMap,PriorityQueue......,这些集合类在单线程环境下使用并没有问题,但是在多线程环境下使用就可能造成线程安全问题。
解决方法也很明确,1.要么手动在使用时候给方法加synchronized,例如:多线程修改ArrayList中的某个值时,给该方法加锁。2.使用标准库提供的一些线程安全的集合类。
下面就介绍一些标准库提供的线程安全的集合类。
ArrayList
vector
这个时一个比较古老的类,但是也是可以保证线程安全的,使用方法和ArrayList类似。
public class VectorDemo {
public static void main(String[] args) {
Vector<Integer> vector = new Vector<>(4);
//打印容量
System.out.println(vector.capacity());
System.out.println("===================");
//添加元素
vector.add(12);
vector.add(15);
vector.add(29);
//打印元素和元素个数
for (Integer x:vector) {
System.out.println(x);
}
System.out.println("有效元素的个数有: "+vector.size());
System.out.println("===================");
//删除元素,可指定位置
vector.remove(1);
//重新打印元素
for (Integer x:vector) {
System.out.println(x);
}
System.out.println("有效元素的个数有: "+vector.size());
}
}
运行结果:
可以看出和ArrayList的使用非常类似,那么Vector是如和实现线程安全的。
其实也就是将一些会造成线程安全问题的方法上加锁。
PriorityQueue
线程安全的队列,我们就可以使用之前学过的BlockingQueue(阻塞队列)。
可以参考博客: http://t.csdn.cn/lPMzS
HashMap
多线程使用哈希表,可以使用 HashTable (不推荐),ConcurrentHashMap (推荐)
HashTable
查看源码可以知道,HashTable保证线程安全也是给方法上加上synchronized。
ConcurrentHashMap
与HashTabe不同的是,ConcurrentHashMap并不是直接在方法上加锁,而是只在发生哈希冲突的当前节点部分加锁,也就是只锁住一个链表(HashMap是通过数组加链表实现的)。
这样就避免”白加锁“的尴尬。也能提高程序的效率。
区别
加锁的粒度不同
不同的加锁粒度导致出发锁冲突的概率不同。
HashTable是针对整个哈希表加锁,在任何的增删查改操作都会触发加锁,也就都有可能出发锁冲突。
假如这个哈希表,现在有两个线程,插入两个数据,通过哈希计算,一个要插入到 0 下标节点的链表中,一个要插入到 2 下标节点的链表中。我们知道,本质上这个操作是针对不同对象的修改操作,并不会导致线程不安全问题。但是如果使用HashTable来进行这个操作,会直接给插入操作加锁,这样就会导致效率降低,而使用ConcurrentHashMap这个操作就不会触发加锁。只有数两个线程的插入数据操作是针对同一个节点的链表时才会加锁。
CAS机制
CAS(compare and swap)
是一种cpu指令,本身就是一个原子操作,无需加锁。
通过比较内存数据(a)和寄存器数据(b)如果一致就将 b值 赋予内存(a)。
在ConcurrentHashMap中有很多方法中都使用到了CAS,CAS本身就是原子的,比加锁更高效。
扩容方式
HashTable:当元素超过当前的容量是触发扩容机制,重新申请一个更大的内存空间,然后将原来旧表中的数据重新进行哈希,插入到新表。但是如果旧表中的数据非常庞大,此时触发了扩容,再使用当前的扩容方式就会使当前的插入操作变得非常卡顿。
ConcurrentHashMap:同样,当put操作触发扩容机制时,会申请一个更大的内存空间,但是并不会一次性将旧表中的所有数据都重新哈希计算重新插入,而是只搬运一部分数据,后面进行查找操作时,会同时查找新,旧表,插入数据时会在新表进行插入。并且在进行每次操作时,都会将旧表中的数据搬运一部分到新表中。