线程安全的集合类
java.util.Hashtable
- (1)Hashtable是一个
散列表,它存储的内容是键值对(key-value)映射
。 - (2)Hashtable继承Dictionary,实现Map、Cloneable、java.io.Serializable接口。
- (3)Hashtable的实例有两个参数影响其性能:初始容量和加载因子。
- 容量是哈希表中桶的数量,
初始容量是哈希表创建时的容量
。 - 在发生“哈希冲突”时,单个桶会存储多个条目,这些条目按顺序搜索。
加载因子是对哈希表在其容量自动增加之前可以达到多满的一个尺度
。默认加载因子是0.75。
- 容量是哈希表中桶的数量,
- Hashtable的函数都是同步的,它是线程安全的,但它的key、value都不可用为null,Hashtable中的映射不是有序的。
- Hashtable提供的几个主要方法,包括get()、put()、remove()等,
都是synchronized修饰的,不会出现两个线程同时对数据进行操作的情况,保证了线程安全
。 - Hashtable实现的Cloneable接口,即实现了clone()方法,clone()方法就是克隆一个Hashtable对象并返回。Hashtable实现的Serializable接口实现类串行读取、写入功能。
Hashtable示例:
public static void main(String[] args){
Hashtable<String, Integer> numbers = new Hashtable<>();
numbers.put("one", 1);
numbers.put("two", 2);
numbers.put("three", 3);
Integer n = numbers.get("two");
if (n != null){
System.out.println("two = " + n);
}
}
运行结果:
two = 2
java.util.concurrent.ConcurrentHashMap
- (1)ConcurrentHashMap继承与AbstractMap,实现了Map、java.io.Serializable接口。
- ConcurrentHashMap是
HashMap的线程安全版
,同Hashtable相比,ConcurrentHashMap不仅保证了访问的线程安全性,而且在效率上也有很大的提高
。 ConcurrentHashMap允许多个修改操作并发运行,关键在于使用了锁分离技术
,即代码块锁
而不是方法锁。它使用了多个锁来控制对hash表的不同部分进行修改。- ConcurrentHashMap内部
使用段(Segment)
来表示不同的而部分,每个段就是一个小的hash table,它们有自己的锁(有ReentrantLock实现),只要多个修改操作发生在不同段上,就可以并发运行。static class Segment<K,V> extends ReentrantLock implements Serializable {}
- ConcurrentHashMap实现的Serializable接口实现类串行读取、写入功能。
ConcurrentHashMap示例:
运行结果:public static void main(String[] args){ ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("one", 1); map.put("two", 2); map.put("three", 3); System.out.println(map.get("two")); if (map.containsKey("one") && map.get("one").equals(1)){ map.remove("one"); } }
2
CopyOnWrite机制介绍
- CopyOnWrite容器是
写时复制的容器
。在往容器添加元素时,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素后,再讲原容器的引用指向新的容器。 - CopyOnWrite容器
可以进行并发地读而不需要加锁。添加元素的时候需要加锁ReentranLock
。 - CopyOnWrite容器是
读写分离的思想,读和写不同的容器
。 - CopyOnWrite的应用场景:
- CopyOnWrite并发容器
用于读多写少的并发场景
。如白名单、黑名单、商品类目的访问和更新等场景。
- CopyOnWrite并发容器
- CopyOnWrite的缺点:
- 内存占用问题:因为CopyOnWrite的写时复制机制,在进行写操作的时候,内存里会同时有两个对象的内存。解决方法是
通过压缩容器中的元素减少大对象的内存消耗
。 - 数据一致性问题: CopyOnWrite容器只能保证数据的
最终一致性
,不能保证数据的实时一致性。
- 内存占用问题:因为CopyOnWrite的写时复制机制,在进行写操作的时候,内存里会同时有两个对象的内存。解决方法是
java.util.concurrent.CopyOnWriteArrayList
- CopyOnWriteArrayList中的set、add、remove等方法,都
使用了ReentrantLock进行加锁,unlock()解锁
。 - CopyOnWriteArrayList在add()方法中,
使用Arrays.copyOf()来拷贝副本
,在副本上增加元素,然后改变原引用指向副本。 - CopyOnWriteArrayList的
读操作不加锁,写操作类实现对其进行了加锁
。public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); ... }
- CopyOnWriteArrayList类是一个线程安全的List接口的实现,
适合运用在读操作远远多于写操作的应用
中,特别在并发情况下,可以提供高性能的并发读取,并且保证读取的内容一定是正确的,不受多线程并发问题影响
。
示例:
public static void main(String[] args){
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("one");
list.add("two");
list.add(1,"three");//list下标从零开始
System.out.println(list.get(1));
if (list.contains("three")){
Iterator<String> value = list.iterator();
while (value.hasNext()){
System.out.println(value.next());
}
}
}
运行结果:
three
one
three
two
java.util.concurrent.CopyOnWriteArraySet
- CopyOnWriteArraySet是在CopyOnWriteArrayList的基础上,使用了java的装饰模式。如存储介质使用了CopyOnWriteArrayList存储数据,remove方法调用CopyOnWriteArrayList的remove方法,add方法调用CopyOnWriteArrayList的addIfAbsent方法。
- CopyOnWriteArrayList的实现原理适用CopyOnWriteArraySet。
- java里面List和Set的相同和不同之处适用于CopyOnWriteArrayList和CopyOnWriteArraySet,后两者都是线程安全机制。
示例:
public static void main(String[] args){
CopyOnWriteArraySet<String> list = new CopyOnWriteArraySet<>();
list.add("one");
list.add("two");
if (list.contains("two")){
Iterator<String> value = list.iterator();
while (value.hasNext()){
System.out.println(value.next());
}
}
}
运行结果:
one
two
Vector
- Vector是矢量队列,通过数组保存数据。它继承了AbstractList,实现了RandomAccess、Cloneable、Serializable等接口。
- Vector继承了AbstractList,实现了List,
Vector是一个队列,支持相关的添加、删除、修改、遍历等操作
。 - Vector实现了RandomAccess接口,实现随机访问功能。RandomAccess是Java中用来被List实现,为List提供快速访问功能的。
在Vector中,可以通过元素的序号快速获取元素对象,即快速随机访问
。 - Vector实现了Cloneable接口,实现克隆功能。
- Vector的操作是
线程安全的,利用Synchronized同步锁的方法实现线程安全
。addElement()、removeElement()、get()等方法都是synchronized修饰。
示例:
public static void main(String[] args){
Vector<String> list = new Vector<>();
list.addElement("one");
list.addElement("two");
list.addElement("three");
list.removeElement("two");
if (list.contains("three")){
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
运行结果:
one
three
StringBuffer和StringBuilder
- StringBuffer是线程安全的,方法加synchronized修饰,StringBuilder不是线程安全的。
- 在高并发的情况下,如果不需要考虑数据安全的问题时,应尽量选择StringBuffer,由于没有资源等待的情况,执行效率和性能会高很多。