如果要并发读写,如果用synchronized和ReentrantLock那会严重阻塞 读-读,读-写,写-写。如果用ReadWriteLock虽然 读-读 ok了,但 读-写 还是阻塞,只要有一个线程写,所有读线程都会阻塞。
CopyOnWriteArrayList 写时复制用空间换时间,读-读 和 读-写 都不再阻塞。适合多读偶尔写,并且允许短暂的读旧数据,不要求强一致性。
注意Object[]有volatile,说明Object[]整个数组引用发生写(替换)时,新数组会对所有线程可见,但Object[]的内容发生写的时候,是不保证新写的内容对其余线程可见。
volatile对其标识的变量或对象引用有效,跟对象内部字段无关。不要想着把对象标识成 volatile 就万事大吉,认为其内部变更能对所有线程可见。
读时直接读Object[],没有任何要求。
写时用lock加锁互斥其他线程的写,复制Object[],在新Object[]里写入后,把Object[]整个替换回去。Object[]有volatile保证新数组的引用对之后的读线程可见。
请注意:lock释放锁时也会将释放锁之前的所有操作,包括写volatile的Object[]操作和任意的写普通共享变量的操作,都刷回主存对其余线程可见。但根据happen-before规则要求其余线程要先获取lock才能完成happen-before链,达到可见性。
但读操作并没有获取lock,不能续上写线程unlock后的happen-before链,所以不能依靠lock的释放-获取达到可见性,只能通过Object[]自己的volatile达到可见性。理解这点很重要,因为马上要分析else里那行看似多余的写Object[]。
走到else这里Object[]无需写, 但Object[]写之前可能会有任意的写普通共享变量的操作(虽然这里没有)。刚才说过读线程没有获取lock所以写线程不能通过unlock来传递所有操作的可见性,于是这里冗余的写volatile的Object[]来传递所有操作的可见性,因为读线程必定读volatile的Object[]。
从java 1.5开始,JSR-133规范要求volatile的写-读达到跟synchronized锁的释放-获取 相同的内存语义。
volatile写之前的操作不能重排序到写之后,volatile写之前的操作在volatile写的时刻一起刷回主存,对其余线程可见。volatile读之后的操作不能重排序到读之前,volatile读的时刻清空处理器高速缓存,所有的读从主存重新读取,完成可见性。
synchronized释放锁的内存语义,等同于volatile写。synchronized获取锁的内存语义,等同于volatile读。
其实RenentrantLock的实现就是对AQS内的volatile int state字段的读写,达到写state(释放lock)之前的所有操作,对别的线程之后的读state(获取lock)可见。然后通过cas写state完成互斥。
网友评论