线程安全
多线程访问时,对数据进行加锁保护,防止数据出现不一致或者数据污染情况。即:当一个线程要访问某类中的数据时,会对其加锁保护,只有当此线程访问完成后,其它线程才能继续访问。
非线程安全
多线程访问数据时,不对数据进行加锁保护,出现数据不一致或者数据污染情况。非线程安全问题我们一般通过synchronized
关键字加锁控制。
关于线程安全和非线程安全问题,我们应该都不陌生。
例如:
请问:
ArrayList
和Vector
有什么区别?HashMap
和HashTable
有什么区别?StringBuilder
和StringBuffer
有什么区别?
回答:ArrayList
是非线程安全的,Vector
是线程安全的;HashMap
是非线程安全的,HashTable
是线程安全的;StringBuilder
是非线程安全的,StringBuffer
是线程安全的;
在问:那么什么是线程安全?和非线程安全的区别是什么?会在什么情况下使用?
回答:.....
这样一套组合拳下来,半条命都没有了,都开始有点怀疑人生了......下面对这些问题进行具体的讲解。
来看看下面的这个非线程安全现象,我们以ArrayList
和Vector
为例
新建一个非线程安全的ArrayList
集合,在建立100个线程,每个线程中都对ArrayList
添加100条数据,那么最终集合中有多少数据呢?
public class MultiThreadListClass {
public static void main(String[] args) {
List<Object> addList = new ArrayList<>();
int threadCount = 100;
int threadCount = 100;
for (int m = 0; m < 10; m++) {
List<Object> addList = new ArrayList<>();
CountDownLatch mCountDownLatch = new CountDownLatch(threadCount);
AddThread addThread = new MultiThreadListClass().new AddThread(mCountDownLatch, addList);
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(addThread);
thread.start();
}
try {
mCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("addListSize:"+ addList.size());
}
}
private class AddThread implements Runnable {
private CountDownLatch countDownLatch;
private List<Object> list;
AddThread(CountDownLatch countDownLatch, List<Object> list) {
this.countDownLatch = countDownLatch;
this.list = list;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
list.add(new Object());
}
countDownLatch.countDown();//完成一个子线程
}
}
}
运行输出结果为:
addListSize:9963
addListSize:10000
addListSize:10000
addListSize:10000
addListSize:9888
addListSize:9865
addListSize:10000
addListSize:10000
Exception in thread "Thread-800" java.lang.ArrayIndexOutOfBoundsException: 14
at java.util.ArrayList.add(ArrayList.java:459)
at com.example.MultiThreadListClass$AddThread.run(MultiThreadListClass.java:93)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-803" java.lang.ArrayIndexOutOfBoundsException: 1400
at java.util.ArrayList.add(ArrayList.java:459)
at com.example.MultiThreadListClass$AddThread.run(MultiThreadListClass.java:93)
at java.lang.Thread.run(Thread.java:745)
从上面的输出结果来看,并不是每次都是10000,有时会造成异常。这就是非线程安全引发的问题了。我们在用Vector
集合来看看输出,更改代码如下:
List<Object> addList = new ArrayList<>();
替换为:
List<Object> addList = new Vector<>();
运行输出结果为:
addListSize:10000
addListSize:10000
addListSize:10000
addListSize:10000
addListSize:10000
addListSize:10000
addListSize:10000
addListSize:10000
addListSize:10000
addListSize:10000
从结果看,每次输出的都是10000,可以在多运行几次进行测试。可见数据一致,也就是线程安全的。在多线程操作Vector
时,不会造成任何问题。我们在用LinkedList
测试下,直接看结果:
addListSize:9966
addListSize:9965
addListSize:10000
addListSize:10000
addListSize:9943
addListSize:10000
addListSize:9963
addListSize:9851
addListSize:9900
addListSize:9908
从结果看,LinkedList
也是非线程安全的。我们在用Collections.synchronizedList
和CopyOnWriteArrayList
测试下,更改代码为:
List<Object> addList = new ArrayList<>();
替换为:
List<Object> addList = new CopyOnWriteArrayList<>();
或者为:
List<Object> addList = Collections.synchronizedList(new ArrayList<>());
运行结果都为10000,也就是这两者是线程安全的。我们来看这两者在读写上的差别,看下面代码:
public class CallableUtils {
private int num;
private int threadSize;
CallableUtils(int num, int threadSize){
this.num = num;
this.threadSize = threadSize;
}
public void add() {
List<Integer> list1 = Collections.synchronizedList(new ArrayList<Integer>());
List<Integer> list2 = new CopyOnWriteArrayList<>();
int addCowTotalTime = 0;
int addCsTotalTime = 0;
ExecutorService executor = Executors.newFixedThreadPool(threadSize);
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
try {
for (int i = 0; i < threadSize; i++) {
addCowTotalTime += executor.submit(new AddCallable(list1, countDownLatch)).get();
}
System.out.println("Collections.synchronizedList add method cost time is "+ addCowTotalTime);
for (int i = 0; i < threadSize; i++) {
addCsTotalTime += executor.submit(new AddCallable(list2, countDownLatch)).get();
}
System.out.println("CopyOnWriteArrayList add method cost time is "+ addCsTotalTime);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public void get() {
List<Integer> list = initList();
List<Integer> list1 = Collections.synchronizedList(list);
List<Integer> list2 = new CopyOnWriteArrayList<>(list);
int addCowTotalTime = 0;
int addCsTotalTime = 0;
ExecutorService executor = Executors.newFixedThreadPool(threadSize);
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
try {
for (int i = 0; i < threadSize; i++) {
addCowTotalTime += executor.submit(new GetCallable(list1, countDownLatch)).get();
}
System.out.println("Collections.synchronizedList get method cost time is "+ addCowTotalTime);
for (int i = 0; i < threadSize; i++) {
addCsTotalTime += executor.submit(new GetCallable(list2, countDownLatch)).get();
}
System.out.println("CopyOnWriteArrayList get method cost time is "+ addCsTotalTime);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
private List<Integer> initList() {
List<Integer> list = new ArrayList<>();
int value = new Random().nextInt(1000);
for (int i = 0; i < num; i++) {
list.add(value);
}
return list;
}
private class AddCallable implements Callable<Integer> {
List<Integer> list;
CountDownLatch countDownLatch;
AddCallable(List<Integer> list, CountDownLatch countDownLatch) {
this.list = list;
this.countDownLatch = countDownLatch;
}
@Override
public Integer call() throws Exception {
long start = System.currentTimeMillis();
int value = new Random().nextInt(1000);
for (int i = 0; i < num; i++) {
list.add(value);
}
long end = System.currentTimeMillis();
countDownLatch.countDown();
return (int) (end - start);
}
}
private class GetCallable implements Callable<Integer> {
List<Integer> list;
CountDownLatch countDownLatch;
GetCallable(List<Integer> list, CountDownLatch countDownLatch) {
this.list = list;
this.countDownLatch = countDownLatch;
}
@Override
public Integer call() throws Exception {
long start = System.currentTimeMillis();
int value = new Random().nextInt(num);
for (int i = 0; i < num; i++) {
list.get(value);
}
long end = System.currentTimeMillis();
countDownLatch.countDown();
return (int) (end - start);
}
}
}
在main
方法中调用
CallableUtils utils = new CallableUtils(10000, 2);
utils.add();
utils.get();
设置数据总数为10000,线程数为2,运行结果为:
Collections.synchronizedList add method cost time is 5
CopyOnWriteArrayList add method cost time is 401
Collections.synchronizedList get method cost time is 2
CopyOnWriteArrayList get method cost time is 1
设置数据总数为10000,线程数为6,运行结果为:
Collections.synchronizedList add method cost time is 8
CopyOnWriteArrayList add method cost time is 1505
Collections.synchronizedList get method cost time is 4
CopyOnWriteArrayList get method cost time is 2
设置数据总数为10000,线程数为10,运行结果为:
Collections.synchronizedList add method cost time is 7
CopyOnWriteArrayList add method cost time is 3893
Collections.synchronizedList get method cost time is 5
CopyOnWriteArrayList get method cost time is 2
设置数据总数为10000,线程数为16,运行结果为:
Collections.synchronizedList add method cost time is 12
CopyOnWriteArrayList add method cost time is 10000
Collections.synchronizedList get method cost time is 9
CopyOnWriteArrayList get method cost time is 2
设置数据总数为10000,线程数为22,运行结果为:
Collections.synchronizedList add method cost time is 14
CopyOnWriteArrayList add method cost time is 19917
Collections.synchronizedList get method cost time is 11
CopyOnWriteArrayList get method cost time is 4
通过上面五组数据可以看出:
- 写数据时,线程数越多,
CopyOnWriteArrayList
花费的时间越多,而Collections.synchronizedList
花费的时间增长缓慢。也就是线程数越多CopyOnWriteArrayList
性能下降非常严重,而Collections.synchronizedList
虽然有下降,但是不严重。 - 读数据时,线程数越多,
CopyOnWriteArrayList
花费的时间慢慢增多,而Collections.synchronizedList
花费时间的增长明显高于CopyOnWriteArrayList
。也就是线程数越多CopyOnWriteArrayList
和Collections.synchronizedList
性能都下降,但是Collections.synchronizedList
下降的速度更明显。
因此在使用这两个集合时,根据自己的需要,做出相应的选择。
网友评论