Java 中 Collections.synchronizedList(List「T」 list) 原理分析

前言

Java 中 ArrayList 和 LinkedList 都不是线程安全的,但可以通过 java.util.Collections.synchronizedList(List list) 方法,获取一个线程安全的 List 实例对象。

设计意图

将非线程安全 List 对象,封装成一个线程安全的 List 对象,处理 List 上的并发性问题。类似一个工具类,减少开发人员的重复性工作。

线程安全测试用例

1.定义个 Obj 类,内部使用一个 Collections.synchronizedList(List) 构造一个线程安全的 List 对象。

 static class Obj {
    private List synchronizedList = Collections.synchronizedList(new ArrayList<>());
    
    public void add(String s) {
         synchronizedList.add(s);
    }
    
    public void remove(String s) {
         synchronizedList.remove(s);
    }
    
    public int size() {
         return synchronizedList.size();
    }
}

2.创建一个 Obj 对象 obj, 两个线程 t1 和 t2,并在 t1 和 t2 中访问 obj 对象,并发的向 obj 对象中添加元素。每个线程循环执行 100 次添加操作。

public class Test {
     public static void main(String[] args) {
         final Obj obj = new Obj();
         Thread t1 = new Thread(new Runnable() {

             @Override
             public void run() {
                 String val = null;
                 for (int i = 0; i < 100; i++) {
                     val = "t1: " + i;
                     obj.add(String.valueOf(val));
                     System.out.println("add: " + val);
                     
                     sleep(50);
                 }
             }
         });

         Thread t2 = new Thread(new Runnable() {

             @Override
             public void run() {
                 String val = null;
                 for (int i = 0; i < 100; i++) {
                     val = "t2: " + (100 + i);
                     obj.add(String.valueOf(val));
                     System.out.println("add: " + val);

                     sleep(50);
                 }
             }
         });
         t1.start();
         t2.start();

         try {
             t1.join(); //等待线程 t1 结束
             t2.join(); //等待线程 t2 结束
         } catch (Exception e) {
         }
         String name = Thread.currentThread().getName();
         System.out.println("Thread name: " + name + ", Obj size: " + obj.size());
     }

     static void sleep(long millis) {
         try {
             Thread.sleep(millis);
         } catch (Exception e) {
             System.out.println("Exception: " + e.getMessage());
         }
     }
    }

在步骤 2 中,最终的输出结果为,

...
...
add: t1: 95
add: t2: 194
add: t2: 195
add: t1: 96
add: t2: 196
add: t1: 97
add: t1: 98
add: t2: 197
add: t2: 198
add: t1: 99
add: t2: 199
Thread name: main, Obj size: 200

可以看到,最终 obj 的 size 为预期的 200

错误用例

如果将 Obj 类中的 List 改为普通的 ArrayList,如下,

static class Obj {
         private List list = new ArrayList<>();

         public void add(String s) {
             list.add(s);
         }

         public void remove(String s) {
             list.remove(s);
         }

         public int size() {
             return list.size();
         }
     }

使用同样的测试用例,最终的输出结果为,

...
...
add: t2: 193
add: t1: 94
add: t2: 194
add: t1: 95
add: t2: 195
add: t1: 96
add: t2: 196
add: t1: 97
add: t2: 197
add: t1: 98
add: t2: 198
add: t1: 99
add: t2: 199
Thread name: main, Obj size: 185

从上面的结果可知,最终 obj 的 size 只有 185,跟我们预期的 200 不一样。表示出现了并发性问题。

原理

正常情况下,Collections.synchronizedList(List list) 返回的是一个 SynchronizedList 的对象,这个对象以组合的方式将对 List 的接口方法操作,委托给传入的 list 对象,并且对所有的接口方法对象加锁,得到并发安全性。
Collections.synchronizedList(List list) 方法源码

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

当传入的 list 是 ArrayList 时,返回 SynchronizedRandomAccessList 对象;传入 LinkedList 时,返回 SynchronizedList 对象。
再来看看 SynchronizedList 源码,

static class SynchronizedList
        extends SynchronizedCollection
        implements List {
        ...
        ...
        final List list;

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

        ...
        ...
        public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }
        public E set(int index, E element) {
            synchronized (mutex) {return list.set(index, element);}
        }
        public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }
        public E remove(int index) {
            synchronized (mutex) {return list.remove(index);}
        }
    ...
    ...   
 }

可以看到,SynchronizedList 的实现里,get, set, add 等操作都加了 mutex 对象锁,再将操作委托给最初传入的 list。

这就是以组合的方式,将非线程安全的对象,封装成线程安全对象,而实际的操作都是在原非线程安全对象上进行,只是在操作前给加了同步锁。

由于有很多业务场景下都有这种需求,所以 Java 类库中封装了这个工具类,给需要的模块使用。

你可能感兴趣的:(Java,基础)