ArrayList(二)线程不安全详解

一、线程安全与线程不安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

二、线程不安全实例

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));
    }


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值