一、线程安全与线程不安全
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
二、线程不安全实例
package com.yc.testArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ArrayListInThread implements Runnable {
//线程不安全
private List<String> threadList = new ArrayList<String>();
//线程安全
//private List<String> threadList = Collections.synchronizedList(new ArrayList<String>());
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 把当前线程名称加入list中
threadList.add(Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
ArrayListInThread listInThread = new ArrayListInThread();
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(listInThread, String.valueOf(i));
thread.start();
}
// 等待子线程执行完
Thread.sleep(2000);
int size = listInThread.threadList.size();
System.out.println("循环100次的元素个数:"+size);
// 输出list中的值
for (int i = 0; i < size; i++) {
if(listInThread.threadList.get(i) == null){
System.out.println();
}
System.out.print(listInThread.threadList.get(i) + " ");
}
}
}
测试结果:
【第一次】
【第二次】
【第三次】
执行几次会发现结果不一样,甚至有时会出现 ArrayIndexOutOfBoundsException
以上执行结果说明ArrayList确实是线程不安全的,然后我们从线程并发的角度分析,为何会出现这样的结果:
结果中有的值没有出现,有的值出现了NULL,这些都是在执行赋值操作的时候出现了覆盖现象。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
赋值语句:elementData[size++] = e ; 可以拆成两条执行
1. elementData[size] = e ;
2. size++;
【出现null和值被覆盖的情况】
假设A 线程执行完第一条语句后,CPU暂停执行A线程转去执行B线程,此时ArrayList的size并没有加一,这是在ArrayList中的B线程就会覆盖掉A线程赋的值,而后,A线程和B线程先后执行size++,便会出现 null 的情况和值被覆盖的情况。
【出现ArrayIndexOutofBoundsException的情况】
当A线程执行ensureCapacityInternal(size + 1) 时没有继续操作,此时恰好minCapacity(就是size+1) 和 elementData.length 相等,B线程再去执行 elementData[size++] = e 并且size++也执行了,此时CPU又去执行A线程剩下的的赋值操作,由于size值加了1,size值大于了ArrayList的最大长度,因此出现数组下标越界。
三、解决方案
List接口下面有两个实现,一个是ArrayList,另外一个是vector。 从源码的角度来看,因为Vector的方法前加了,synchronized关键字,也就是同步的意思,sun公司希望Vector是线程安全的,而希望arraylist是高效的。
在源码中的体现,在多线程项目中sun公司推荐我们使用 List<Object> list =Collections.synchronizedList(new ArrayList<Object>) 来创建一个ArrayList对象。
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}