Vector、HashTable、HashMap和ConcurrentHashMap
《java并发编程》的第二章2.1. 什么是线程安全性 讲到线程安全性的时候给出了一个例子考虑下面的代码片段,它迭代一个Vector 中的元素。尽管Vector 的所有方法都是同步的,但是在多线程的环境中不做额外的同步就使用这段代码仍然是不安全的,因为如果另一个线
程恰好在错误的时间里删除了一个元素, 则get() 会抛出一个ArrayIndexOutOfBoundsException 。
Vector v = new Vector();
// contains race conditions -- may require external synchronization
for (int i=0; i<v.size(); i++) {
doSomething(v.get(i));
}
这里发生的事情是:get(index) 的规格说明里有一条前置条件要求 index 必须是非负的,并且小于 size() 。但是,在多线程环境中,没有办法可以知道上一次查到的size() 值是否仍然有效,因而不能确定 i<size() ,除非在上一次调用了 size() 后独占地锁定Vector。
于是编写代码试验之,(开始主要是想试一下,但是发现试不出来。原理看了api都懂了,可是后来就是和这个测试方法较真了。)
package com.lan; import java.util.Vector; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import lombok.AllArgsConstructor; public class runn { static ExecutorService exec = Executors.newFixedThreadPool(1000); static CyclicBarrier barrier = new CyclicBarrier(3); public static void main(String[] args) { try{ for (int k = 0; k < 500; k++) { Vector v = new Vector<Integer>(); for (int i = 0; i < 2; i++) { v.add(i); } // System.out.println("main size"+v.size()); for (int i = 0; i < v.size(); i++) { v.get(i); // System.out.println(i+" main."+v.get(i)); } // if(k==20) // v.remove(20); exec.submit(new read(v,barrier)); exec.submit(new delete(v,barrier)); try { barrier.await(); // Thread.sleep(50); } catch (Exception e) { e.printStackTrace(); } System.out.println("--------次数:"+k+"-------------"); } Thread.sleep(1000); exec.shutdown(); }catch(Exception e){ e.printStackTrace(); System.exit(0); } } } @AllArgsConstructor class read implements Runnable{ private Vector v; private CyclicBarrier barrier; public void run() { try { for (int i = 0; i < v.size(); i++) { v.get(i); // System.out.println(i+"read-"+v.get(i)); } } catch (Exception e) { e.printStackTrace(); }finally{ try { barrier.await(); } catch (Exception e) { e.printStackTrace(); } } } } @AllArgsConstructor class delete implements Runnable{ private Vector v; private CyclicBarrier barrier; public void run() { try { v.remove(1); // System.out.println("delete: "+v.remove(v.size()-1)); } catch (Exception e) { e.printStackTrace(); }finally{ try { barrier.await(); } catch (Exception e) { e.printStackTrace(); System.out.println("delete error"); } } } }
却一直测不出来,可能是次数不够多也有关系,后来终于被我得到和下面一样的结果了。所以改进了一下程序的意图,使的不原子性更加明显。
package com.lan; import java.util.Vector; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import lombok.AllArgsConstructor; public class runn { static ExecutorService exec = Executors.newFixedThreadPool(1000); public static void main(String[] args) throws Exception { Vector v = new Vector<Integer>(); for (int i = 0; i < 5; i++) { v.add(i); } System.out.println("main size"+v.size()); for (int i = 0; i < v.size(); i++) { System.out.println(i+" main."+v.get(i)); } exec.submit(new read(v)); exec.submit(new delete(v)); } } @AllArgsConstructor class read implements Runnable{ private Vector v; public void run() { try { System.out.println("read size "+v.size()); for (int i = 0; i < v.size(); i++) { System.out.println("i:"+i); } for (int i = 0; i < v.size(); i++) { if(i==4){ Thread.sleep(500); } System.out.println(i+"read-"+v.get(i)); } } catch (Exception e) { e.printStackTrace(); } } } @AllArgsConstructor class delete implements Runnable{ private Vector v; public void run() { try { Thread.sleep(450); System.out.println("delete: "+v.remove(4)); } catch (InterruptedException e) { e.printStackTrace(); } } }
结果如下:
main size5 0 main.0 1 main.1 2 main.2 3 main.3 4 main.4 read size 5 i:0 i:1 i:2 i:3 i:4 0read-0 1read-1 2read-2 3read-3 delete: 4 java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 4 at java.util.Vector.get(Vector.java:744) at com.lan.read.run(runn.java:42) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744)
明明说vector是线程安全的,这里为什么又出现了这样的问题呢?ctrl+左击,查看源代码,发现vector类的几乎所有方法都是synchronized修饰的,但是注意到一个细节
public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); }
在if判断的前后也是可以有其它线程对这个elementData进行操作的,锁住的是这个方法,而不是vector对象。所以,vector和HashTable一样,严格意义上来说都是线程不安全的类。但是对于并发量不大的情况下,这种不安全非常的难以出现。(从前面用不同的方式测试了非常多次也可以发现这一点)
HashMap的就简单多了,就只保证单线程时有正确的行为,它本身就不是线程安全的,但是正因为没有添加synchronized内部锁,不需要持有和释放锁,所以它的性能要优越于vector和HashTable。
待续。。。(完)
我只能说ConcurrentHashMap的原理是不简单的,拥有更高程度的并发性,线程安全也更为可靠。以后可以单独对ConcurrentHashMap
开一章。复杂度可见:http://www.blogjava.net/wuxufeng8080/articles/152238.html