再学习多线程编程的时候,看到如下代码。
package com.cbf4life; import java.util.*; public class ThreadSafeDemo { public ThreadSafeDemo() { ThreadGroup group=new ThreadGroup("testGroup"); MyThread at=new MyThread(); for(int i=0;i<10000;i++){ Thread th=new Thread(group,at,String.valueOf(i)); th.start(); } while (group.activeCount() > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(at.list0.size()); System.out.println(at.list0.get(0)); } public static void main(String[] args) { new ThreadSafeDemo(); } class MyThread implements Runnable { List<String> list0=new ArrayList<String>(); //thread not safe // Vector<String> list0=new Vector<String>(); //thread not safe // List<String> list0=Collections.synchronizedList(new ArrayList<String>()); //thread safe public void run() { try { Thread.sleep((int)(Math.random()*2)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } list0.add(Thread.currentThread().getName()); } } }
这段代码的原意是举例说明ArrayList是线程不安全的,然后在运行的时候可能会出现多种结果,
第一种结果
9976
0
大致原因是list的add方法导致的。
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }这个是list的add方法,
elementData[size++] = e;
这一段代码块就是问题所在,size++这个过程可以分为三步
1.从内存中取到size的值。
2.计算size的值。
3.将size的值写到内存当中。
这不是一个原子性的操作,当多线程添加的情况下,假如size=0时,添加第一个元素的线程添加成功,同时在size没有及时写到内存中时,这时另一个线程继续添加就覆盖了原来的值导致最终list的值为9976。
第二种结果
9976
1
按道理说结果应该是9976 0,但是还是上面说的原因,原来的0被1覆盖了。
第三种结果
数组下标越界异常
原因可能出现在数组扩容的时候,假如当前数组的大小为8,而我添加第7个元素的时候,数组当前不需要扩容。然后这个线程进入阻塞,另一个线程也添加元素,发现当前数组大小还有一个容量,于是添加成功已经有8个元素,然后前一个阻塞线程开始添加元素,然后数组没有扩容,所以数组就容不下这个元素,于是数组下标越界了。
这个例子是在说明ArrayList的非线程安全的特性。