1 同步容器出现的原因
常见的容器中,如:HashMap、ArrayList、LinkedList、HashSet 等都不是线程安全的容器。
所以,Java 提供了 HashTable、Vector 等同步的容器供用户使用。
2 Java中的同步容器类
在Java中,同步容器主要包括2类:
1)Vector、Stack、HashTable
2)Collections类中提供的静态工厂方法创建的类
Vector实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施。
Stack也是一个同步容器,它的方法也用synchronized进行了同步,它实际上是继承于Vector类。
HashTable实现了Map接口,它和HashMap很相似,但是HashTable进行了同步处理,而HashMap没有。
Collections类是一个工具提供类,注意,它和Collection不同,Collection是一个顶层的接口。在Collections类中提供了大量的方法,比如对集合或者容器进行排序、查找等操作。最重要的是,在它里面提供了几个静态工厂方法来创建同步容器类,如下图所示:
3 同步容器的缺陷
从同步容器的具体实现源码可知,同步容器中的方法采用了synchronized进行了同步,那么很显然,这必然会影响到执行性能,另外,同步容器就一定是真正地完全线程安全吗?不一定,这个在下面会讲到。
我们首先来看一下传统的非同步容器和同步容器的性能差异,我们以ArrayList和Vector为例:
3.1 性能问题
我们先通过一个例子看一下Vector和ArrayList在插入数据时性能上的差异:
package sync_container;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Vector;
/**
* Author: HeatDeath
* Date: 2018/2/5
* Desc:
*/
public class SyncContainer {
public static void singleThread() {
ArrayList<Integer> single_list = new ArrayList<Integer>();
Vector<Integer> single_vector = new Vector<Integer>();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++)
single_list.add(i);
long end = System.currentTimeMillis();
System.out.println("ArrayList进行【10000000】次插入操作耗时:" + (end - start) + "ms");
start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++)
single_vector.add(i);
end = System.currentTimeMillis();
System.out.println("Vector进行【10000000】次插入操作耗时:" + (end - start) + "ms");
System.out.println("--------------------------------------------------------");
}
public static void mulThread() {
final ArrayList<Integer> list = new ArrayList<Integer>();
final Vector<Integer> vector = new Vector<Integer>();
for (int i = 0; i < 10; i++) {
Thread thread1 = new Thread() {
public void run() {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++)
list.add(i);
long end = System.currentTimeMillis();
System.out.println("ArrayList进行100000次插入操作耗时:" + (end - start) + "ms");
}
};
thread1.start();
}
for (int i = 0; i < 10; i++) {
Thread thread2 = new Thread() {
public void run() {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++)
vector.add(i);
long end = System.currentTimeMillis();
System.out.println("Vector进行100000次插入操作耗时:" + (end - start) + "ms");
}
};
thread2.start();
}
}
public static void main(String[] args) {
SyncContainer.singleThread();
SyncContainer.mulThread();
}
}
分别以单线程和多线程的方式测试了 ArrayList 和 Vector 两种容器。
运行结果如下:
ArrayList进行【10000000】次插入操作耗时:3958ms
Vector进行【10000000】次插入操作耗时:1593ms
--------------------------------------------------------
Exception in thread "Thread-2" Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 9369
at java.util.ArrayList.add(ArrayList.java:463)
at sync_container.SyncContainer$1.run(SyncContainer.java:37)
java.lang.ArrayIndexOutOfBoundsException: 549
at java.util.ArrayList.add(ArrayList.java:463)
at sync_container.SyncContainer$1.run(SyncContainer.java:37)
Exception in thread "Thread-8" java.lang.ArrayIndexOutOfBoundsException: 31618
at java.util.ArrayList.add(ArrayList.java:463)
at sync_container.SyncContainer$1.run(SyncContainer.java:37)
Exception in thread "Thread-9" java.lang.ArrayIndexOutOfBoundsException: 31618
at java.util.ArrayList.add(ArrayList.java:463)
at sync_container.SyncContainer$1.run(SyncContainer.java:37)
Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: 106710
at java.util.ArrayList.add(ArrayList.java:463)
at sync_container.SyncContainer$1.run(SyncContainer.java:37)
Exception in thread "Thread-6" java.lang.ArrayIndexOutOfBoundsException: 240097
at java.util.ArrayList.add(ArrayList.java:463)
at sync_container.SyncContainer$1.run(SyncContainer.java:37)
ArrayList进行100000次插入操作耗时:44ms
ArrayList进行100000次插入操作耗时:44ms
ArrayList进行100000次插入操作耗时:56ms
ArrayList进行100000次插入操作耗时:54ms
Vector进行100000次插入操作耗时:77ms
Vector进行100000次插入操作耗时:92ms
Vector进行100000次插入操作耗时:104ms
Vector进行100000次插入操作耗时:104ms
Vector进行100000次插入操作耗时:107ms
Vector进行100000次插入操作耗时:107ms
Vector进行100000次插入操作耗时:111ms
Vector进行100000次插入操作耗时:111ms
Vector进行100000次插入操作耗时:116ms
Vector进行100000次插入操作耗时:115ms
Process finished with exit code 0
在数据量较大的时候,且为【单线程】的情况,Vector 比 ArrayList 的性能更好,Vector 上锁解锁的时间可以忽略不计。而 Vector 和 ArrayList 都是可变数组,当容量不足的时候,ArrayList 是容量增大 1.5 倍,而 Vector 是容量增大 2 倍。Vector 在增加容量上节省了更多的时间。
3.2 同步容器真的是安全的吗?
也有有人认为Vector中的方法都进行了同步处理,那么一定就是线程安全的,事实上这可不一定。看下面这段代码:
package sync_container;
import java.util.Vector;
/**
* Author: HeatDeath
* Date: 2018/2/5
* Desc:
*/
public class VectorTest {
static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for(int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
for(int i=0;i<vector.size();i++)
vector.remove(i);
};
};
Thread thread2 = new Thread(){
public void run() {
for(int i=0;i<vector.size();i++)
vector.get(i);
};
};
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}
运行结果如下:
Exception in thread "Thread-10397" Exception in thread "Thread-10389" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 3
at java.util.Vector.get(Vector.java:748)
at sync_container.VectorTest$2.run(VectorTest.java:25)
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 10
at java.util.Vector.get(Vector.java:748)
at sync_container.VectorTest$2.run(VectorTest.java:25)
Exception in thread "Thread-10417" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 20
at java.util.Vector.get(Vector.java:748)
at sync_container.VectorTest$2.run(VectorTest.java:25)
Exception in thread "Thread-10461" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 10
at java.util.Vector.get(Vector.java:748)
at sync_container.VectorTest$2.run(VectorTest.java:25)
Exception in thread "Thread-10757" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9
at java.util.Vector.get(Vector.java:748)
at sync_container.VectorTest$2.run(VectorTest.java:25)
Exception in thread "Thread-11035" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 15
at java.util.Vector.get(Vector.java:748)
at sync_container.VectorTest$2.run(VectorTest.java:25)
Exception in thread "Thread-11051" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 21
正如大家所看到的,这段代码报错了:数组下标越界。
也许有朋友会问:Vector是线程安全的,为什么还会报这个错?很简单,对于Vector,虽然能保证每一个时刻只能有一个线程访问它,但是不排除这种可能:
当某个线程在某个时刻执行这句时:
for(int i=0;i<vector.size();i++)
vector.get(i);
假若此时vector的size方法返回的是10,i的值为9
然后另外一个线程执行了这句:
for(int i=0;i<vector.size();i++)
vector.remove(i);
将下标为9的元素删除了。
那么通过get方法访问下标为9的元素肯定就会出问题了。
因此为了保证线程安全,必须在方法调用端做额外的同步措施,如下面所示:
package sync_container;
import java.util.Vector;
/**
* Author: HeatDeath
* Date: 2018/2/5
* Desc:
*/
public class VectorTest {
static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while (true) {
for (int i = 0; i < 10; i++)
vector.add(i);
Thread thread1 = new Thread() {
public void run() {
synchronized (VectorTest.class) { //进行额外的同步
for (int i = 0; i < vector.size(); i++)
vector.remove(i);
}
}
};
Thread thread2 = new Thread() {
public void run() {
synchronized (VectorTest.class) {
for (int i = 0; i < vector.size(); i++)
System.out.println(vector.get(i));
}
}
};
thread1.start();
thread2.start();
while (Thread.activeCount() > 10) {
}
}
}
}
Java并发编程:同步容器
http://www.cnblogs.com/dolphin0520/p/3933404.html