你知道synchronizedList和Vector底层原理实现和区别吗?其实开始我也不知道!

疑问:同样都是加了锁的集合,为什么synchronizedList比Vector快呢?


探究ArrayList为什么查询快、增删慢,实现add方法底层原理详解
ArrayList源码分析(基于JDK8)


因为Vector和ArrayList除了数组扩容有点差别,还有加锁使Vector迈进了线程安全的行列外,底层实现大约是没有太大区别的!基本一致!线程安全问题更是另当别论了!继续往下看就OK!


扩容的区别:

从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。


Vector是java.util包下的一个集合。而synchronizedList是java.util.Collections中的一个静态内部类。首先先看一下怎么使用!

	//synchronizedList
	List<Object> list = new ArrayList<Object>();
	List<Object> safeList Collections.synchronizedList(list);
	//Vector
	List<Object> vector = new Vector<Object>();

那它们两个都是线程安全的集合为什么synchronizedList比Vector快呢?两个有什么区别呢?让我们带着小问号去追一下源码!

这里我们就举一个例子,拿集合当中常用的add方法说事。

Vector add的实现方法

//注意:Vector是在方法上加的锁!
public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
}

private void ensureCapacityHelper(int minCapacity) {
        // 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 + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
}

synchronizedList add的实现方法

//注意:synchronizedList是用同步代码块给传入的集合对象加锁!
public boolean add(E e) {
      synchronized (mutex) {return c.add(e);}
}

从我在源码中标记“注意”总结:Vector使用同步方法实现,synchronizedList使用同步代码块实现!

首先、详解剖析synchronizedList。Collections句点synchronizedList之后可以放入一个集合这是它的用法!而我们点入synchronizedList时,进入的是Collections类,出现的源码是以下这样的!

public static <T> List<T> synchronizedList(List<T> list) {
   return (list instanceof RandomAccess ?
        new SynchronizedRandomAccessList<>(list) :
        new SynchronizedList<>(list));
}

当你传入一个集合对象时,先进行三元表达式判断是否是一个支持随机访问的类型。无论是与否(不关注),都会给你new(创建)一个对象,把你传入的集合作参数放在这个对象构造方法中!
而这时我们点进入一个SynchronizedList继续追!

SynchronizedList(List<E> list) {
        super(list);
        this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
        super(list, mutex);
        this.list = list;
}

看见的即是SynchronizedList静态内部类的两个构造方法。而传入的参数即是我们要传入的普通集合!第二个构造方法传入的是一个集合还有一个mutex。而在源码中可以看出 ,把你传入的list参数给了父类,即super(list)。又把参数赋给了自己!即是每个构造方法里都有this.list = list; 。
而类中的list是最终的所有类型的List集合,即:final List< E > list;
然后再去看看它的父类对他做了什么!

SynchronizedCollection(Collection<E> c) {
        this.c = Objects.requireNonNull(c);
        mutex = this;
}

SynchronizedCollection(Collection<E> c, Object mutex) {
        this.c = Objects.requireNonNull(c);
        this.mutex = Objects.requireNonNull(mutex);
}

它在类中定义了一个父接口(Collection< E >)的final集合,即:final Collection c;(给集合包装起来)。而第二个构造方法中也是这样操作的,但都多出了一个Object类型的mutex参数,即:final Object mutex; (要在其上同步的对象);
具体操作呢?就是首先this.c = Objects.requireNonNull©;判断父接口集合接收的你的参数是不是null(空对象)

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

是空的话抛出空指针异常,不是空的话this.c = Objects.requireNonNull©;将传入的集合参数c赋给父接口定义的集合this.c ,然后继续往下走走就到了这里,即:mutex = this;。mutex就是定义的Object类型对象,而this赋给了mutex。那么this是什么呢?this就是内部类SynchronizedCollection< E >对象实例。这番操作就是将自身的内部类对象实例赋给了mutex。而mutex在add方法中发挥了很大的作用!充当了一个重要的角色!向下看~

public boolean add(E e) {
        synchronized (mutex) {return c.add(e);}
}

在这段源码中给mutex对象加了锁,从而把让它也迈进了线程安全的行列!
而我们在使用的时候是这样的:

List<Object> list = new ArrayList<Object>();
List<Object> safeList Collections.synchronizedList(list);

用一个名为safeList的List集合去接收这个线程安全的集合对象,从而可以使用safeList add方法功能!
而你使用safeList去调用add方法的时,传入你原来的list集合参数,你真正使用的safeList实际上是这个工具synchronizedList给你提供的一个地址,然后你的所有的操作(包括add)都必须经过静态内部类SynchronizedCollection< E >以上的一番加工包装操作,其实而这个内部类的属性mutex才是你开始传入的参数集合。

因为ArrayList集合是线程不安全的集合,你经过这一系列的包装,而你使用包装后的safeList去调用add方法操作时,你把要添加的这个参数(比如:你要添加一个Integer类型的数字)传给了内部类对象,而这个内部类对象去调用ArrayList集合的add方法!举个栗子!

List<Object> list = new ArrayList<Object>();		//比如:ArrayList集合对象地址为0x112233
List<Object> safeList Collections.synchronizedList(list);		//比如:包装后的safeList集合对象地址为0x445566

我们创建的ArrayList(0x112233)集合对象交给了Collections.synchronizedList加工包装,而我们在调用add方法时,是用加工后的SynchronizedCollection类型的safeList(0x445566)去调用的。传入的Integer类型数字,你原ArrayList集合通过包装后final Collection c; 类型c调用的add方法。最后,看下面一条代码!

synchronized (mutex) {return c.add(e);}

当调用add方法的时候,你必须拿到(对象锁标记——mutex)锁才能进行add操作!而这个mutex锁标记(final的实例变量)锁的对象只是一个你传入的一个集合对象。而当很多线程执行添加操作(add操作)并发访问时,哪个线程能拿到这个对象唯一的实例变量mutex(锁标记)才能对集合进行添加操作(add操作)!

这样以来摇身一变,不影响ArrayList内部方法操作,而且还变成了一个线程安全的集合,所以它的效率才比Vector要快的!


同步代码块和同步方法的区别:

因为SynchronizedList只是使用同步代码块包裹了ArrayList的方法,而ArrayList和Vector中同名方法的方法体内容并无太大差异,所以在锁定范围和锁的作用域上两者并无却别。 在锁定的对象区别上,SynchronizedList的同步代码块锁定的是mutex对象,Vector锁定的是this对象
而其实mutex对象就是SynchronizedList有一个构造函数可以传入一个Object类型对象,如果在调用的时候显示的传入一个对象,那么锁定的就是用户传入的对象。如果没有指定,那么锁定的也是this对象。结果就是SynchronizedList可以指定锁定的对象

基于我们将ArrayList转成SynchronizedList。那么如果我们想把LinkedList变成线程安全的,或者说我想要方便在中间插入和删除的同步的链表,那么我可以将已有的LinkedList直接转成SynchronizedList,而不用改变他的底层数据结构。而这一点是Vector无法做到的,因为他的底层结构就是使用数组实现的,这个是无法更改的


SynchronizedList和Vector区别:

  • SynchronizedList有很好的扩展和兼容功能。他可以将所有的List的子类转成线程安全的类。
    使用SynchronizedList的时候,进行遍历时要手动进行同步处理
  • SynchronizedList可以指定锁定的对象

发布了109 篇原创文章 · 获赞 166 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_44170221/article/details/104881810