线程安全容器类

原创
2013/05/02 21:10
阅读数 979
AI总结

     线程安全:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

常见的容器类
线程安全类:Vector和Hashtable
线程非安全类:ArrayList和HashMap

HashMap实例:

public class MainClass {
	public static Map<String, String> hashMap = new HashMap<String, String>();

	public static void main(String[] args) throws InterruptedException {
		// hashMap = Collections.synchronizedMap(hashMap);
		Thread t1 = new Thread() {
			public void run() {
				for (int i = 0; i < 25; i++) {
					hashMap.put(String.valueOf(i), String.valueOf(i));
				}
			}
		};

		Thread t2 = new Thread() {
			public void run() {
				for (int j = 0; j < 25; j++) {
					hashMap.put(String.valueOf(j), String.valueOf(j));
				}
			}
		};

		t1.start();
		t2.start();

		Thread.sleep(1000);

		for (int i = 0; i < 25; i++) {
			System.err.println(i + ":" + hashMap.get(String.valueOf(i)));
		}
	}
}
打印的结果不稳定,经常出现错误的值
0:null
1:1
2:2
3:null
4:4
5:null


原因分析:

public V put(K key, V value) {
...
addEntry(hash, key, value, i);
...
}

void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    if (size++ >= threshold)
       resize(2 * table.length);
}

void resize(int newCapacity) {
      Entry[] oldTable = table;
      int oldCapacity = oldTable.length;
      if (oldCapacity == MAXIMUM_CAPACITY) {
          threshold = Integer.MAX_VALUE;
          return;
      }

      Entry[] newTable = new Entry[newCapacity];
      transfer(newTable);
      table = newTable;
      threshold = (int)(newCapacity * loadFactor);
}

从代码中,可以看到,如果发现哈希表的大小超过阀值threshold,就会调用resize方法,扩大容量为原来的两倍,而扩大容量的做法是新建一个Entry[]。 
如果在默认情况下,一个HashMap的容量为16,加载因子为0.75,那么阀值就是12,所以在往HashMap中put的值到达12时,它将自动扩容两倍,如果两个线程同时遇到HashMap的大小达到12的倍数时,就很有可能会出现在将oldTable转移到newTable的过程中遇到问题,从而导致最终的HashMap的值存储异常。

JDK1.0引入了第一个关联的集合类HashTable,它是线程安全的。HashTable的所有方法都是同步的。
JDK2.0引入了HashMap,它提供了一个不同步的基类和一个同步的包装器synchronizedMap。synchronizedMap被称为有条件的线程安全类。
JDK5.0util.concurrent包中引入对Map线程安全的实现ConcurrentHashMap,比起synchronizedMap,它提供了更高的灵活性。同时进行的读和写操作都可以并发地执行。

ArrayList实例:

public static List<String> list = new ArrayList<String>();

	public static void main(String[] args) throws InterruptedException {
//		list = Collections.synchronizedList(list);
		Thread t1 = new Thread() {
			public void run() {
				for (int i = 0; i < 25; i++) {
					list.add(String.valueOf(i));
				}
			}
		};

		Thread t2 = new Thread() {
			public void run() {
				for (int j = 25; j < 50; j++) {
					list.add(String.valueOf(j));
				}
			}
		};

		t1.start();
		t2.start();

		Thread.sleep(1000);

		for (int i = 0; i < 50; i++) {
			System.err.println(i + ":" + list.get(i));
		}
	}
结果出现异常:
44:48
45:49
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 46, Size: 46
at java.util.ArrayList.RangeCheck(ArrayList.java:547)
at java.util.ArrayList.get(ArrayList.java:322)

at map.Test.main(Test.java:37)

原因分析:

public boolean add(E e) {
	ensureCapacity(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
    }

在多线程的操作下导致扩容和赋值操作的不统一导致了出错。
util.concurrent包也提供了一个线程安全的ArrayList替代者CopyOnWriteArrayList。

Vector和Hashtable在各自的方法上都加了synchronized关键字来保持同步。
我们往往把同步的概念仅仅的理解为一种互斥的方式。其实同步不仅可以阻止一个线程看到对象处于不一致的状态中,它还可以保证进入同步方法或同步代码块的每个线程,都看到由同一个锁保护的之前所有的修改效果。

展开阅读全文
加载中
点击加入讨论🔥(1) 发布并加入讨论🔥
1 评论
1 收藏
0
分享
AI总结
返回顶部
顶部