简要概括
java的锁两种底层逻辑——synchronized关键字和lock接口。synchronized底层是jvm层面,lock底层是AQS,是java层面。
还有就是不上锁,cas。
用synchronized来实现的是vector和hashTable。
AQS与synchronized比较
synchronized是关键字,是jvm的优化。使用更加简单,但用法固定。低并发量、同步需求简单时使用。
AQS是java代码的优化。程序员可以自定义操作,用法更加灵活,但使用起来更复杂困难。高并发量、同步需求复杂时使用。
为什么高并发量aqs相比于synchronized更好呢?
- aqs支持可中断,减少等待时间。
tryLock,如果没获取到锁,不会继续阻塞等待,让渡时间给其他线程
- aqs自旋cas更多,能尽量减少上下文切换。
虽然synchronized的jvm底层也有cas,但次数已经定死了,aqs是java代码层面的,cas次数更多,就更有机会获取到锁,获取到锁就不会阻塞,就不会在操作系统层面进行上下文切换,所以cas次数多,上下文切换的概率就低些
- aqs功能更多,更能满足高并发中复杂的需求。
aqs支持独占模式、共享模式、条件变量等,synchronized只支持独占模式。
arraylist
a线程遍历list时,如果b线程同时add、remove了list,a线程会报ConcurrentModifiedException并发修改错误,因为a中的modCount值变了。
当前线程在遍历迭代器,如果其他线程有add、remove操作,当前线程会报错ConcurrentModifiedException并发修改错误,set操作不会报错。这是因为arraylist有弱一致性,需要保证list容器数量的一致性。
底层实现原理是,遍历的时候,如果有其他线程add数据了,list的modCount就会更改,遍历的时候会适时比较这个值是否有变动,如果有变动就会报错。
如果不想报错,又要并发,解决方案如下。
写时并发异常解决方案:
vector——synchronized
在arraylist的add方法上加了synchronized,保证串行,一次只有1个线程能操作list。
CopyOnWriteArrayList——ReentrantLock、在副本上进行编辑操作
a遍历的是原数组,b修改的是副本,a不去比较modCounta,a不会报错。
当有add、remove、set操作时,都会获取ReentrantLock锁,一次只有一个线程能改数据,然后复制原本数组生成副本,在副本上修改数据,然后把老数组的引用移到新数组上,因为引用是volatile的,所以其他线程对更新后的数据可见。
遍历的时候,如果add、remove不会报错,因为add、remove方法重写了,不会去改modCount所以就不会报ConcurrentModifiedException。
CopyOnWriteArrayList与vector比较:
读取性能CopyOnWriteArrayList好:对CopyOnWriteArrayList读取的时候,不管有没有线程对CopyOnWriteArrayList进行写操作,都不会阻塞读。对vector读取的时候,如果刚好有线程对vector进行写操作,因为有synchronized,对象被上锁,只允许指定线程运行,所以读取会被阻塞。
set性能vector好:因为CopyOnWriteArrayList要复制整个数组,vector不用。
add、remove性能vector、CopyOnWriteArrayList一样。底层都是复制数组。
所以,如果只是add、remove,没有set操作,用CopyOnWriteArrayList。否则就读多写少,用CopyOnWriteArrayList。读写均衡/读少写多,用vector。