最近写代码的时候傲然F3点进去看了一下源代码,看了之后有几个问题,然后找资料解决了,记录一下。
CopyOnWriteArrayList在写的时候复制整个数组,这样操作其实比较耗时,所以这个List适合写少读多的并发场景,此时有很好的性能。 读操作(get(),indexOf(),isEmpty(),contains())不加任何锁,而写操作(set(),add(),remove())通过Arrays.copyOf()操作拷贝当前底层数据结构(array),在其上面做完增删改等操作,再将新的数组置为底层数据结构,同时为了避免并发增删改, CopyOnWriteList在这些写操作上通过一个ReetranLock进行并发控制。
数据结构:Object数组(含有get和set方法)
关于读和写:
链表中单个元素的get操作,直接返回数组中对于的数据。
1
2
3
|
public E get( int index) {
return (E)(getArray()[index]);
}
|
链表中元素的设置set操作,先copy一份,然后添加元素,之后再调用setArray来重新赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public E set( int index, E element) {
//加锁操作
final ReentrantLock lock = this .lock;
lock.lock();
try {
Object[] elements = getArray();
//获取原先数据
Object oldValue = elements[index];
//比较新旧数据是否相同
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics这里一般看不懂
setArray(elements);
}
return (E)oldValue;
} finally {
lock.unlock();
}
} |
1、在set中有个疑问,就是为啥在新老对象相同的时候,还调用了一下setArray函数呢?
http://cs.oswego.edu/pipermail/concurrency-interest/2010-February/006886.html
从注释来看,是确保volatile的写的语义。至于为啥,看了邮件列表中,应该是为了确保happends-before特性。
http://blog.csdn.net/fg2006/article/details/6397142
2、看了一下,貌似这个方法调用的比较多System.arraycopy,Arrays.copyof 里面也是调用的这个方法,这个方法是干啥的?
这个在System中是一个native方法,负责数组拷贝,参数简单明了。
1
2
3
4
5
6
7
8
9
|
/* @param src the source array. * @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
|
写了个程序对比了一下使用System和自己循环进行数据拷贝的效率问题,还是原生的给力啊。
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
|
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListTest {
public static void main(String[] args) {
int size = 1000 ;
//每次copy运行的次数
int runTime = 100000 ;
String[] src = genString(size);
String[] destSysCopy = new String[size];
String[] destSelfloop = new String[size];
/**通过System.arrayCopy来复制*/
long s1 = System.currentTimeMillis();
for ( int i= 0 ;i<runTime;i++){
System.arraycopy(src, 0 , destSysCopy, 0 , src.length);
}
System.out.println( "arrayCopy:" +(System.currentTimeMillis()-s1)+ ",size=" +destSysCopy.length);
/**通过循环来完成复制*/
long s2 = System.currentTimeMillis();
for ( int i= 0 ;i<runTime;i++){
for ( int j= 0 ;j<src.length;j++){
destSelfloop[j] = src[j];
}
}
System.out.println( "selfLoop:" +(System.currentTimeMillis()-s2)+ ",size=" +destSelfloop.length);
}
/**初始化算是数组*/
private static String[] genString( int size){
String[] a = new String[size];
for ( int i= 0 ;i<size;i++){
a[i] = "iamzhongyong+" +i;
}
return a;
}
} |
1
2
|
arrayCopy: 78 ,size= 1000
selfLoop: 453 ,size= 1000
|
3、关于迭代器
CopyOnWriteList所实现的迭代器其数据也是底层数组镜像,所以在CopyOnWriteList进行interator,同时并发增删改CopyOnWriteList里的数据实不会抛“ConcurrentModificationException”,当然在迭代器上做remove,add,set也是无效的(抛UnsupportedOperationExcetion),因为迭代器上的数据只是当前List的数据数组的一个拷贝而已。
1
2
3
|
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0 );
}
|
1
2
3
4
5
6
7
8
9
10
|
public void remove() {
throw new UnsupportedOperationException();
}
public void set(E e) {
throw new UnsupportedOperationException();
}
public void add(E e) {
throw new UnsupportedOperationException();
}
|
4、为啥数据对象加了volatile,不加有啥问题?
private volatile transient Object[] array;
Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。(http://www.infoq.com/cn/articles/ftf-java-volatile )
如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。
Volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。
锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。
互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。
可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。
Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。
Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。
至此能够清楚了,array的值在写的时候,通过synchronized来保证原子性,但是此时还有别的线程可能用到了array这个对象,例如在读的时候,这时候通过volatile能够保证可见性。
5、CopytOnWriteArrayList中写方法使用RentrantLock来实现同步,为啥不用synchronized呢?
这个可能是历史原因,在jdk6之前,synchronized在多线程情况下吞吐量下降和明显,而Lock的话会好很多,所以并发包中采用了Lock的机制,后来JDK6对于synchronized锁做了优化,两者持平,所以正在synchronized能够满足需求的情况下,优先考虑使用这个,毕竟非常方便吗。
重入锁:指的是同一个线程多次试图获取它所占有的锁,请求会成功。当释放锁的时候,直到重入次数清零,锁才释放完毕。
搞个例子对比一下现在的情况,例子参照:http://www.blogjava.net/killme2008/archive/2007/09/14/145195.html
Counter接口:
1
2
3
4
|
public interface Counter {
public long getValue();
public void increment();
} |
三种情况下的实现:
1
2
3
4
5
6
7
8
9
10
11
|
public class NoLockCounter implements Counter {
private long count = 0 ;
@Override
public long getValue() {
return count;
}
@Override
public void increment() {
count++;
}
} |
1
2
3
4
5
6
7
8
9
10
11
|
public class SynchronizeCounter implements Counter {
private long count = 0 ;
@Override
public long getValue() {
return count;
}
@Override
public synchronized void increment() {
count++;
}
} |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class ReentranLockCounter implements Counter {
private volatile long count = 0 ;
private Lock lock = new ReentrantLock();
@Override
public long getValue() {
return count;
}
@Override
public void increment() {
lock.lock();
try {
count++;
} finally {
//切记在finally中释放锁,synchronize的话JVM会自动释放锁
lock.unlock();
}
}
} |
单线程情况下的测试:
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
|
public class LockTest {
public static void main(String[] args) {
//单线程情况下基本没啥插件,synchronize略强一点,无锁最好,这个不用说呵呵
int runTime = 100000000 ;
Counter c1 = new SynchronizeCounter();
Counter c2 = new ReentranLockCounter();
Counter c3 = new NoLockCounter();
// 单线程情况下测试
long s1 = System.currentTimeMillis();
for ( int i= 0 ;i<runTime;i++){
c1.increment();
}
System.out.println( "synchronize锁,C1:" +c1.getValue()+ ",用时:" +(System.currentTimeMillis()-s1));
long s2 = System.currentTimeMillis();
for ( int i= 0 ;i<runTime;i++){
c2.increment();
}
System.out.println( "ReentranLock锁,C2:" +c2.getValue()+ ",用时:" +(System.currentTimeMillis()-s2));
long s3 = System.currentTimeMillis();
for ( int i= 0 ;i<runTime;i++){
c3.increment();
}
System.out.println( "无锁,C3:" +c3.getValue()+ ",用时:" +(System.currentTimeMillis()-s3));
}
} |
多线程情况测试,原生锁和重入锁差别不大,无锁有现成安全问题:
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
37
38
|
import java.util.concurrent.CountDownLatch;
public class BenchmarkTest {
private Counter counter;
public static void main(String[] args) throws Exception{
Counter c1 = new SynchronizeCounter();
Counter c2 = new ReentranLockCounter();
Counter c3 = new NoLockCounter();
//这里和参照例子不同,我用来CountDownLatch来做现成同步
CountDownLatch latch1 = new CountDownLatch( 1000 );
CountDownLatch latch2 = new CountDownLatch( 1000 );
CountDownLatch latch3 = new CountDownLatch( 1000 );
//100线程,每个线程加100,增把数据增加到10000
long s1 = System.currentTimeMillis();
for ( int i= 0 ;i< 1000 ;i++){
new TestThread(c1,latch1).start();
}
latch1.await();
System.out.println( "SynchronizeCounter的结果是:" +c1.getValue()+ ",用时:" +(System.currentTimeMillis()-s1));
long s2 = System.currentTimeMillis();
for ( int i= 0 ;i< 1000 ;i++){
new TestThread(c2,latch2).start();
}
latch2.await();
System.out.println( "ReentranLockCounter结果是:" +c2.getValue()+ ",用时:" +(System.currentTimeMillis()-s2));
long s3 = System.currentTimeMillis();
for ( int i= 0 ;i< 1000 ;i++){
new TestThread(c3,latch3).start();
}
latch3.await();
System.out.println( "无锁的结果是:" +c3.getValue()+ ",用时:" +(System.currentTimeMillis()-s3));
}
} |
1
2
3
|
SynchronizeCounter的结果是: 100000 ,用时: 125
ReentranLockCounter的结果是: 100000 ,用时: 125
无锁的结果是: 99999 ,用时: 109
|
相关推荐
CopyOnWriteArrayList容器的代码实现主要基于ReentrantLock加锁机制来保证线程安全。在add操作中,首先将原容器copy一份,然后在新副本上执行添加操作,最后将原容器引用指向新副本。在remove操作中,首先将原容器...
《Java疯狂讲义》第三版源代码是一份深入学习Java编程的重要参考资料,它涵盖了Java语言的基础、进阶以及实际应用的各个层面。这份源代码旨在帮助读者通过实践理解书中讲解的理论知识,提升编程技能。以下将对其中...
《JVM高级特性与最佳实践(第2版)》是一本深入探讨Java虚拟机(JVM)技术的书籍,其源代码提供了丰富的实践案例和示例,帮助读者更直观地理解JVM的工作原理和优化技巧。以下是根据书名和描述所涉及的一些关键知识点...
Java 类的源代码文件是程序员编写程序的基本单元,它们以.java为扩展名,包含了Java语言的语法结构,用于定义对象、方法和变量等。在这个压缩包中,我们可能找到了作者自己编写的关于Java集合类的实例源代码。Java...
《JAVA实例精通源代码》是李相国等专家编著的一本书籍的配套源码集合,旨在通过实际的编程示例帮助读者深入理解和掌握Java语言。这个压缩包中包含了书中各个章节的实例代码,让我们来详细探讨这些源代码中涵盖的知识...
《Core Java 2》是Java开发领域的一本经典著作,分为卷一《基础知识》和卷二《高级特性》,深入浅出地介绍了Java编程的核心概念和技术。...同时,书中丰富的示例代码和源代码分析有助于加深理解,提升实战能力。
本文将深入探讨Java多线程设计模式及其源代码,旨在帮助开发者理解和应用这些模式,提升代码的并发性能和可维护性。 1. **生产者消费者模式**:该模式基于`BlockingQueue`,例如`ArrayBlockingQueue`,用于在生产...
4. **集合框架**:Java集合框架包括List、Set、Map等接口及其实现类,源代码会展示如何使用ArrayList、LinkedList、HashSet、HashMap等,以及并发集合如ConcurrentHashMap和CopyOnWriteArrayList的使用场景。...
这个压缩包文件包含了多种Java源代码实例,是初学者探索和学习Java编程的理想资源。以下将详细解析这些源代码可能涉及的知识点,以帮助你更好地理解和应用Java。 1. **基础语法**:所有编程语言都有其基本的语法...
java遍历时可修改的容器CopyOnWriteArrayList
《JVM并发编程源代码深度解析》 JVM(Java Virtual Machine)是Java程序的运行环境,它负责解释和执行字节码,是Java平台的核心组成部分。并发编程是利用多核处理器的优势,使得多个任务可以同时执行,提高系统效率...
《Java并发编程实践》源代码分析 在Java编程领域,多线程是不可或缺的一部分,它使得程序能够同时执行多个任务,提升系统效率。《Java并发编程实践》这本书深入浅出地探讨了Java平台上的并发问题,提供了许多实用的...
【标题】"第一行代码Java源代码第13章课程代码Java类集框架"涉及到的是Java编程中的核心概念——类集框架。这个标题表明我们将会深入学习Java编程语言中的类库,特别是关于集合框架的部分,这在实际开发中至关重要。...
通过张孝祥老师的"多线程实例+源代码",你可以亲自动手实践这些概念,加深对多线程的理解。每个文件如"ThreadTest2"都可能包含一个或多个具体的线程编程实例,通过这些实例,你可以看到如何在实际场景中应用上述知识...
本教程的"Java实例 - 集合输出源代码+详细指导教程.zip"旨在帮助开发者深入理解Java集合框架,并通过实例源代码进行实践学习。 集合框架包括接口、类和算法,主要由以下部分组成: 1. **接口**:这些是集合的抽象...
Java中的`CopyOnWriteArrayList`是一个线程安全的列表实现,特别适合于高并发环境下的读多写少的场景。这个类的名字暗示了其工作原理:在修改(写入)时复制原有的数组,然后在新的数组上进行操作,最后将新数组替换...
"JavaExamples2快速点击·所有集合源代码"显然是一份包含与Java集合相关的示例代码的压缩包,旨在帮助开发者更好地理解和运用Java集合框架。下面我们将详细探讨Java集合框架中的主要组件以及它们的应用。 1. **...
《实战JAVA高并发程序设计源代码汇总版》是针对JAVA并发编程领域的一份宝贵资源,它集合了《实战JAVA高并发程序设计》一书中的核心示例代码,旨在帮助开发者深入理解和实践JAVA并发编程的关键技术。这个压缩包包含了...
这个压缩包“Java集合案例及源代码.rar”显然是一个教学资源,包含了丰富的实例和详细的注释,旨在帮助教师进行教学准备,同时也非常适合学生用来复习和深化对Java集合的理解。 1. **Java集合接口**: - **List**...