早在jdk1.1版本中,所有的集合都是线程安全的。
但是在1.2以及之后的版本中就出现了一些线程不安全的集合,为什么版本升级会出现一些线程不安全的集合呢?
因为线程不安全的集合普遍比线程安全的集合效率高的多。随着业务的发展,特别是在web应用中,为了提高用户体验减少用户的等待时间,页面响应速度(也就是效率)是优先考虑的。而且对线程不安全的集合加锁后也能达到安全的效果(但是效率会低,因为会有锁的获取已经等待)。
其实在jdk源码中相同效果的集合线程安全的比线程不安全的就多了一个同步机制,但是效率上却低了不止一点点,因为效率低,所以已经不太建议使用了。下面举一些常用的功能相同却线程安全和不安全的集合。
Vector:就比ArrayList多了个同步化机制(线程安全)。
Hashtable:就被HashMap多了个线程安全。
ConcurrentHashMap:是一种高效但是线程安全的集合。
Stack:栈,也是线程安全的,继承于Vector,已过时,不建议使用。
java.util.concurrent包提供了映像、有序集合和队列的高效实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentLinkedQueue。这些集合通过复杂的算法,通过允许并发的访问数据结构的不同部分来时竞争极小化。
这些集合返回弱一致性的迭代器,这意味着迭代器不一定能反映出他们被构造之后的所有的修改,但是,他们不会将同一个值返回两次,也不会抛出ConcurrentModificationException的异常。
import java.util.*;
import java.util.concurrent.*;
/*
* ConcurrentLinkedQueue是“线程安全”的队列,而LinkedList是非线程安全的。
*
* 下面是“多个线程同时操作并且遍历queue”的示例
* (01) 当queue是ConcurrentLinkedQueue对象时,程序能正常运行。
* (02) 当queue是LinkedList对象时,程序会产生ConcurrentModificationException异常。
*
* @author skywang
*/
public class Main {
// TODO: queue是LinkedList对象时,程序会出错。
//private static Queue queue = new LinkedList();
private static Queue<String> queue = new ConcurrentLinkedQueue<String>();
public static void main(String[] args) {
// 同时启动两个线程对queue进行操作!
new MyThread("ta").start();
new MyThread("tb").start();
}
private static void printAll() {
String value;
Iterator iter = queue.iterator();
while(iter.hasNext()) {
value = (String)iter.next();
System.out.print(value+", ");
}
System.out.println();
}
private static class MyThread extends Thread {
MyThread(String name) {
super(name);
}
@Override
public void run() {
int i = 0;
while (i++ < 6) {
// “线程名” + "-" + "序号"
String val = Thread.currentThread().getName()+i;
queue.add(val);
// 通过“Iterator”遍历queue。
printAll();
}
}
}
}
我们明白,为了确保在单线程环境下的性能最大化,所以基础的集合实现类都没有保证线程安全。那么如果我们在多线程环境下如何使用集合呢?
当然我们不能使用线程不安全的集合在多线程环境下, 这样做会导致出现我们期望的结果。我们可以手动添加synchronized代码块来确保安全,但是使用自动线程安全的线程比我们手动更为明智。
我们知道,Java集合框架提供了工厂方法创建线程安全的集合,这些方法的格式如下:
Collections.synchronizedXXX(collection)
这个工厂方法封装了指定的结婚并返回了一个线程安全的集合。XXX可以是Collection、List、Map、Set、SortedMap、SortedSet的实现类。比如下面这段代码创建了一个线程安全的列表:
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
如果我们已经拥有了一个线程不安全的集合,我们可以通过以下方法来封装成线程安全的集合:
Map<Integer, String> unsafeMap = new HashMap<>();
Map<Integer, String> safeMap = Collections.synchronizedMap(unsafeMap);
如你所见,工厂方法封装指定的集合,返回一个线程安全的集合。事实上接口基本都一直,只是实现上添加了synchronized来实现。所以被称为:同步封装器。后面集合的工作都是由这个封装类来实现。
在我们使用iterator来遍历线程安全的集合对象的时候,我们还是需要添加synchronized字段来确保线程安全,因为iterator本身并不是线程安全的,请看代码如下:
这样会抛出ConcurrentModificationException的异常!
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
// adds some elements to the list
Iterator<String> iterator = safeList.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
}
事实上我们应该这样来写:
如果在另一个线程可能进行修改时,要对集合进行迭代,仍然需要使用锁,如下:
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
new Thread(new Runnable() {
@Override
public void run() {
synchronized (safeList) {
for (int i = 0; i < 110; i++) {
safeList.add(i + "");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Iterator<String> iterator = safeList.iterator();
synchronized (safeList) {
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
}
}
}
}).start();