1. 9IM首页
  2. 热点

使ArrayList,LinkedList变成线程安全的

1.使用SynchronizedList

SynchronizedList是一个线程安全的包装类。继承于SynchronizedCollection,SynchronizedCollection实现了Collection接口,SynchronizedList包含一个List对象,对List的访问修改方法进行了一些封装,在封装的方法中会对list使用同步锁加锁,然后再进行存取和修改操作。

使用方法如下

LinkedList<Integer>    linkedList    = new LinkedList<Integer>();
//调用Collections的synchronizedList方法,传入一个linkedList,会返回一个SynchronizedList实例对象
List<Integer> synchronizedList =  Collections.synchronizedList(linkedList);

//调用Collections的synchronizedList方法,ArrayList,返回一个SynchronizedRandomAccessList实例对象
ArrayList<Integer>    arrayList    = new ArrayList<Integer>();
List<Integer> synchronizedRandomAccessList =  Collections.synchronizedList(linkedList);

(Collections.synchronizedList()方法会判断传入的对象是否实现了 RandomAccess接口,是的话,会返回一个SynchronizedRandomAccessList对象,SynchronizedRandomAccessList是SynchronizedList的子类,只是会多一个以线程安全的方式获取子数组的方法)。

SynchronizedList类的部分代码如下:

 static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
        final List<E> list;//源list
       final Object mutex; 

        SynchronizedCollection(Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            mutex = this;//mutex就是SynchronizedList实例自己,作为同步锁使用
        }
        
				public E get(int index) {
            synchronized (mutex) {
            是父类中的成员变量,在父类中会将list赋值给mutex
            		return list.get(index);
            }
        }
        public E set(int index, E element) {
            synchronized (mutex) {return list.set(index, element);}
        }
}

2.使用CopyOnWriteArrayList

CopyOnWriteArrayList跟ArrayList类似,都是实现了List接口,只不过它的父类是Object,而不是AbstractList。CopyOnWriteArrayList与ArrayList的不同在于,

1.内部持有一个ReentrantLock类型的lock成员变量,

	final transient ReentrantLock lock = new ReentrantLock();

在对数组进行修改的方法中,都会先获取lock,获取成功才能进行修改,修改完释放锁,保证每次只允许一个线程对数组进行修改。

2.CopyOnWriteArrayList内部用于存储元素的Object数组使用volatile

   //CopyOnWriteArrayList
   private transient volatile Object[] array;
    
   //ArrayList
   private transient Object[] elementData;//transient

可以看到区别主要在于CopyOnWriteArrayList的Object是使用volatile来修饰的,volatile可以使变量具备内存可见性,一个线程在工作内存中对变量进行修改后,会立即更新到物理内存,并且使得其他线程中的这个变量缓存失效,其他线程在读取会去物理内存中读取最新的值。(volatile修饰的是指向数组的引用变量,所以对数组添加元素,删除元素不会改变引用,所以为了保证内存可见性,CopyOnWriteArrayList.add()方法在添加元素时,都是复制出一个新数组,进行修改操作后,再设置到就数组上)

注意事项:Object数组都使用transient修饰是因为transient修饰的属性不会参与序列化,ArrayList通过实现writeObject()和readObject()方法来自定义了序列化方法(基于反序列化时节约空间考虑,如果用默认的序列方法,源elementData数组长度为100,实际只有10个元素,反序列化时也会分配长度为100的数组,造成内存浪费。)

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
	//1. 使用Lock,保证写线程在同一时刻只有一个
    lock.lock();
    try {
		//2. 获取旧数组引用
        Object[] elements = getArray();
        int len = elements.length;
		//3. 创建新的数组,并将旧数组的数据复制到新数组中
        Object[] newElements = Arrays.copyOf(elements, len + 1);
		//4. 往新数组中添加新的数据	        
		newElements[len] = e;
		//5. 将旧数组引用指向新的数组
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

3.SynchronizedList和CopyOnWriteArrayList优缺点

SynchronizedList是通过对读写方法使用synchronized修饰来实现同步的,即便只是多个线程在读数据,也不能进行,如果是读比较多的场景下,会性能不高,所以适合读写均匀的情况。

而CopyOnWriteArrayList是读写分离的,只对写操作加锁,但是每次写操作(添加和删除元素等)时都会复制出一个新数组,完成修改后,然后将新数组设置到旧数组的引用上,所以在写比较多的情况下,会有很大的性能开销,所以适合读比较多的应用场景。

原创文章,作者:9IM,如若转载,请注明出处:https://www.9im.cn/500.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注