CopyOnWriteArrayList的并发实现
锁机制的采用:final transient ReentrantLock lock = new ReentrantLock();
元素的存储:private transient volatile Object[] array;
,采用了volatile
修饰,确保array的在多线程下的可见性
并发下的设置元素,采用了java.util.concurrent
包的ReentrantLock
锁(创建时采用了默认的不公平锁),在设置新元素时,是获取了原数组的一个拷贝Object[] elements = getArray();
,在拷贝的数组上进行元素的更改,在更改完成后,通过setArray
方法更新旧的array数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public E set (int index, E element) { final ReentrantLock lock = this .lock; lock.lock(); try { Object[] elements = getArray(); E oldValue = get(elements, index); if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { setArray(elements); } return oldValue; } finally { lock.unlock(); } }
特点:CopyOnWriteArrayList对于数据的更改的反馈是延迟性的,从上面一个示例代码可以看出,由于是通过数组的copy操作,在拷贝的副本上进行相应的数据操作,然后在进行副本替代原数据的操作,从而实现ArrayList的并发操作;因此,当两个线程同时对CopyOnWriteArrayList进行操作时,假设A线程遍历CopyOnWriteArrayList,B线程在对CopyOnWriteArrayList进行数据的修改,A线程所遍历的还是旧数据,是无法立马感知线程B对CopyOnWriteArrayList的修改操作
final ReentrantLock lock = this.lock 为什么要多此一举? 在看CopyOnWriteArrayList
时,对其中的一段代码很是不解
1 2 3 4 public E set (int index, E element) { final ReentrantLock lock = this .lock; lock.lock(); }
当时搞不清楚为什么要把lock
这个全局变量再次复值给一个final
的ReentrantLock
,通过一些博客的解析之后,终于明白了
解答问题 测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class MemoryTest { private final ReentrantLock lock = new ReentrantLock(); public MemoryTest () { } public void test () { lock.lock(); lock.unlock(); } public void test2 () { final ReentrantLock l = this .lock; l.lock(); l.unlock(); } public static void main (String[] args) { MemoryTest m = new MemoryTest(); m.test(); m.test2(); } }
对上面的代码进行编译生成class文件后,我们对class文件进行查看
javap -verbose MemoryTest.class
通过查看常量池的内容,可以看到,lock
这个成员变量是在常量池里的
常量池内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 Constant pool: #1 = Methodref #11.#26 // java/lang/Object."<init>":()V #2 = Class #27 // java/util/concurrent/locks/ReentrantLock #3 = Methodref #2.#26 // java/util/concurrent/locks/ReentrantLock."<init>":()V #4 = Fieldref #7.#28 // MemoryTest.lock:Ljava/util/concurrent/locks/ReentrantLock; #5 = Methodref #2.#29 // java/util/concurrent/locks/ReentrantLock.lock:()V #6 = Methodref #2.#30 // java/util/concurrent/locks/ReentrantLock.unlock:()V #7 = Class #31 // MemoryTest #8 = Methodref #7.#26 // MemoryTest."<init>":()V #9 = Methodref #7.#32 // MemoryTest.test:()V #10 = Methodref #7.#33 // MemoryTest.test2:()V #11 = Class #34 // java/lang/Object #12 = Utf8 lock #13 = Utf8 Ljava/util/concurrent/locks/ReentrantLock; #14 = Utf8 a #15 = Utf8 I #16 = Utf8 <init> #17 = Utf8 ()V #18 = Utf8 Code #19 = Utf8 LineNumberTable #20 = Utf8 test #21 = Utf8 test2 #22 = Utf8 main #23 = Utf8 ([Ljava/lang/String;)V #24 = Utf8 SourceFile #25 = Utf8 MemoryTest.java #26 = NameAndType #16:#17 // "<init>":()V #27 = Utf8 java/util/concurrent/locks/ReentrantLock #28 = NameAndType #12:#13 // lock:Ljava/util/concurrent/locks/ReentrantLock; #29 = NameAndType #12:#17 // lock:()V #30 = NameAndType #35:#17 // unlock:()V #31 = Utf8 MemoryTest #32 = NameAndType #20:#17 // test:()V #33 = NameAndType #21:#17 // test2:()V #34 = Utf8 java/lang/Object #35 = Utf8 unlock
通过查看编译后字节码后,我们可以发现,在test
方法中,由于直接使用成员变量,因此有两次getfield
操作,这是从常量池中获取lock
这个成员变量,而java内存情况是,方法执行是是在虚拟机栈中的,而常量池是在方法区中的,需要从堆中拿到指针然后压入栈中,因此,由于test
方法是直接使用成员变量,因此会有而外的开销;而test2
方法中,由于将成员变量赋值给了方法内的局部变量,相比test
方法,少了一次的指针从堆拿取然后入栈的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public void test () ; descriptor: ()V flags: (0x0001 ) ACC_PUBLIC Code: stack=1 , locals=1 , args_size=1 0 : aload_0 1: getfield #4 // Field lock:Ljava/util/concurrent/locks/ReentrantLock; 4: invokevirtual #5 // Method java/util/concurrent/locks/ReentrantLock.lock:()V 7 : aload_0 8: getfield #4 // Field lock:Ljava/util/concurrent/locks/ReentrantLock; 11: invokevirtual #6 // Method java/util/concurrent/locks/ReentrantLock.unlock:()V 14 : return LineNumberTable: line 13 : 0 line 14 : 7 line 15 : 14 public void test2 () ; descriptor: ()V flags: (0x0001 ) ACC_PUBLIC Code: stack=1 , locals=2 , args_size=1 0 : aload_0 1: getfield #4 // Field lock:Ljava/util/concurrent/locks/ReentrantLock; 4 : astore_1 5 : aload_1 6: invokevirtual #5 // Method java/util/concurrent/locks/ReentrantLock.lock:()V 9 : aload_1 10: invokevirtual #6 // Method java/util/concurrent/locks/ReentrantLock.unlock:()V 13 : return LineNumberTable: line 18 : 0 line 19 : 5 line 20 : 9 line 21 : 13