前段时间写过一篇关于concurrentHashMap的文章ConcurrentHashMap实现原理,其中讲到了实现ConcurrentHashMap的原理,这篇文章就讲一下CopyOnWrite的实现原理。Java中提供了两个CopyOnWrite容器,分别是CopyOnWriteArrayList和CopyOnWriteArraySet。如果想要高效的使用这两个容器,我觉的首先要弄懂CopyOnWrite的原理。那么下面就先来谈谈什么是CopyOnWrite吧。
什么是CopyOnWrite
CopyOnWrite即写时复制,该机制在于控制对数据的操作,什么时候需要对数据的操作进行控制呢,当然是在并发的时候,如果程序只是单线程的,所有对数据的操作都是顺序执行,那么数据的一致性就自然能得到保证。但是当有多个线程并发对数据进行操作的时候,情况就没那么简单了,一个线程修改了数据,另一个线程在数据被修改的过程中又读取了数据并对它进行修改,然后把修改的结果写回主内存,于是就会丢失一个线程对数据所做的修改。这种情况可以通过线程同步来解决,但是同步代码会大大降低并发效率。那么,有没有什么更好的方法来解决这个问题呢,当然,这个方法就是CopyOnWrite!首先我们可以来分析一下对数据的并发访问有哪些情况,如果我们仔细一想就会发现,并发访问数据的情况也无非下面这几种:读读、读写、写读、写写。针对这几种情况,可以分别对并发效率进行优化。
- 读读这种情况是不需要加锁的,多个线程可以同时读取数据。
- 读写这种情况便要考虑到对数据一致性的要求高低,我们可以将读锁和写锁分离,读取数据时不加锁,修改数据时加写锁,这样相当于只同步了执行写操作的线程,但是由于写线程在修改数据时,读线程仍然可以读取数据,所以这样会造成一定程度的数据不一致。如果对数据一致性要求比较高,可以在修改数据时同时加写锁和读锁,这样写线程在修改数据时,读线程也必须等待。
- 写读这种情况,同样可以使用读写锁分离的方式,但是更好的方法就是使用CopyOnWrite,对读线程不加锁,写线程修改数据时,先把原数据复制一份进行修改,读线程仍然读到的是旧数据,修改完之后再将原数据的引用指向新的数据,注意这时候写线程仍然是需要加锁的,否则多个线程将会复制出多个副本。
- 写写这种情况就只好进行同步了,但是仍然可以通过降低锁的粒度来进行优化。
其实上面所讲到的这些,不仅仅是在Java多线程并发处理领域有应用,在数据库并发事务处理方面也有广泛应用,这篇文章数据库事务与其隔离级别中有相关概念的介绍。
CopyOnWrite适用场景
CopyOnWrite机制适用于读多写少的场景,比如搜索引擎对某些关键词的过滤使用的黑名单,黑名单很久才会更新一次,但是几乎每时每刻都在被读取。这种机制不适用于对数据实时性要求较高的场景中,因为一个线程修改了数据,其他线程并不一定能够马上读取到新的数据。