Vector和ArrayList的本质区别到底是什么?

昨天又看人讨论这个问题,发个帖,为新手整理思路,正本清源。

很多人面试、被面试的时候都会被反复问到这个蛋疼的问题:Vector和ArrayList区别是什么?(同理StringBuffer & StringBuilder,Hashtable & HashMap,etc.)

我想很多人都会说出这个以讹传讹了好多年的标准答案:Vector是同步的,ArrayList不是。

其实最主要的核心差别是(抛开API层面的接口关系不讲),JDK1.4之前的这些类,API里面的public函数,都加了synchronized关键字,而JAVA5之后的ArrayList之类的结构,则取消了这个同步关键字限制。

 

取消是必然的,因为这个“同步”,现实在并发环境中起不到作用。

为了把问题简单一点,我们来看一个这样的结构。

public class MyList {
  private int length = 0;

  public synchronized int add(Object newElement) {
    //做添加的操作,blablabla
     length++;
  }

  public synchronized int size() {
    return this.length;
  }
  
}

好的,现在有一个MyList的实例,myList,被某线程的两个实例并发访问,线程A和线程B。

我们看看如果该线程类的实例,线程A和B都执行这样代码:

public void run() {
  if (myList.size() == 0) {
  //BTW:ArrayList里面应该用isEmpty()代替
    myList.add(new Element());
  }

}

 

让我们看看这个MyList类的代码是Thread Safe的吗?假设一个如下的执行顺序:

1.线程A执行myList.size(),这时候线程B也进入了myList.size(),但是只能等线程A的myList.size()执行完毕

2.线程A的myList.size()执行完毕,再没有执行add之前,线程B执行了myList.size()

3.线程A执行了add,无论如何,这时候线程B也要执行add了,因为都满足了myList.size() == 0

run()函数里面的逻辑并没有正确的被执行。

 

synchronized 加在非静态函数上,相当于instance作为锁,即等价于:

synchronized (this) {
 //code...
}

这个锁的粒度非常不好,即难以保证类内部的成员变量在并发下保持一致(如果有多个成员变量的话),也没法保证外面调用者的宏观逻辑是正确的,反倒降低了整体性能。

所以在JAVA5之后,JAVA把这个事情扔给了程序员,数据结构只是作为基础代码存在。

 

更具体的说,很多朋友都知道StringBuilder性能比StringBuffer好不少,其实作为项目整体层面来说,锁和拷贝是降低性能的罪魁祸首,尤其对于性能要求很极端的项目,应该尽量减少不必要的锁和拷贝;当然sun是提供api的,就更不能加入这样的不必要的同步代码了。

 

开发当中,如果一定需要锁,可以为具体的要保护的无关系的成员变量单独分配锁,这样可以保证获得最大的性能。

 

其实JAVA5还提供了很多用于并发的数据结构,比如ConcurrentMap的putIfAbsent就在一个比较好的粒度上给简化了程序员的代码,不会犯前面这个反例的错误。

 

将来,如果在scala、F#等FP语言能够普及,程序员也许不必再考虑这么多的并发问题。

你可能感兴趣的:(多线程,数据结构,编程,Hibernate,面试)