HashMap和HashTable
我们知道HashMap是线程不安全的,在多线程环境下,使用HashMap进行put操作时,可能会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。
HashTable和HashMap的实现原理几乎一样,差别无非是HashTable不允许key和value为null,且HashTable是线程安全的。但是HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put等所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。
ConcurrentHashMap
在JDK1.5以后,java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。ConcurrentHashMap就是其中一种比较常用的类,它是一种线程安全的哈希表,对于多线程的操作,介于HashMap和HashTable之间,其内部采用“分段锁”机制代替了HashTable的“独占锁”,从而大大提高了性能。
分段锁机制
我们知道Map的结构,是采用数组+链表的结构实现的(JDK1.8改采用数组+链表+红黑树实现),而ConcurrentHashMap采用了一种“分段锁”来实现的。
ConcurrentHashMap
ConcurrentHashMap使用Segment(分段锁)技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。所以说,ConcurrentHashMap在并发情况下,不仅保证了线程安全,而且提高了性能。
CopyOnWriteArrayList
在java.util.concurrent包中还提供了设计用于多线程上下文中的Collection实现,如ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList和CopyOnWriteArraySet。当期望许多线程访问一个给定collection时,ConcurrentHashMap通常优于同步的HashMap,ConcurrentSkipListMap通常优于同步的TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的ArrayList。
接下来简单看一下这个CopyOnWriteArrayList
12345678910111213141516171819
public class CopyOnWriteArrayListDemo implements Runnable { private static ListString list = Collections.synchronizedList(new ArrayList()); static { list.add("a"); list.add("b"); list.add("c"); } @Override public void run() { IteratorString iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); list.add("a"); } }}
public class CopyOnWriteArrayListDemo implements Runnable {
private static ListString list = Collections.synchronizedList(new ArrayList());
static {
list.add("a");
list.add("b");
list.add("c");
}
@Override
public void run() {
IteratorString iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
list.add("a");
}
}
}
123456789
public class CopyOnWriteArrayListTest { public static void main(String[] args) { CopyOnWriteArrayListDemo copyOnWriteArrayListDemo = new CopyOnWriteArrayListDemo(); for (int i = 0; i 10; i++) { new Thread(copyOnWriteArrayListDemo).start(); } }}
public class CopyOnWriteArrayListTest {
public static void main(String[] args) {
CopyOnWriteArrayListDemo copyOnWriteArrayListDemo = new CopyOnWriteArrayListDemo();
for (int i = 0; i 10; i++) {
new Thread(copyOnWriteArrayListDemo).start();
}
}
}
测试运行结果
在程序中,我们创建一个ArrarList,并通过Collections工具类将其转换为线程安全的类,然后创建10个线程,进行迭代并添加。程序运行结果报错,这个错的意思是,并发修改异常,也就是在程序中,因为是同步的,所以一个线程在迭代数据的同时,又要进行添加操作,那么就会出现此异常。那么我们如何解决这个问题呢?
1234567891011121314151617181920
public class CopyOnWriteArrayListDemo implements Runnable { // private static ListString list = Collections.synchronizedList(new ArrayList()); private static CopyOnWriteArrayListString list = new CopyOnWriteArrayList(); static { list.add("a"); list.add("b"); list.add("c"); } @Override public void run() { IteratorString iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); list.add("a"); } }}
public class CopyOnWriteArrayListDemo implements Runnable {
// private static ListString list = Collections.synchronizedList(new ArrayList());
private static CopyOnWriteArrayListString list = new CopyOnWriteArrayList();
static {
list.add("a");
list.add("b");
list.add("c");
}
@Override
public void run() {
IteratorString iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
list.add("a");
}
}
}
此时再次运行程序,发现异常没有了。那CopyOnWriteArrayList是如何做到的呢?先看一下一小段源码。
1234567891011121314
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(); }}
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的添加操作,可以看出,在每次进行添加操作的时候,CopyOnWriteArrayList底层都是重新copy了一份数组,再往新的数组中添加数组,待添加完了,再将新的数组覆盖原来的数组。
那么既然每次添加元素的时候,都会重新复制一份新的数组,那就带来了一个问题,就是增加了内存的开销。所以,在应用的时候,CopyOnWriteArrayList并不适合做添加操作。但是如果在并发场景下,迭代操作比较频繁,那CopyOnWriteArrayList是个不错的选择。