1. 什么是悲观锁、乐观锁?
- 乐观锁执行操作前认为不会导致冲突,操作数据时,不进行任何特殊处理,在进行更新后,才会去判断是否有冲突。
- 悲观锁操作数据时,认为操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作。
2. 悲观锁和乐观锁(CAS)应用场景?
乐观锁适用于资源竞争较少的情况下,也就是冲突很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,用悲观锁比较合适。
3. CAS是什么(原理)?是线程安全的不?有什么问题?
- CAS是compare and swap的缩写,意思是比较交换。CAS 操作包含三个操作数(V,E,N) —— V表示要更新的变量、E表示预期值,N表示新值。如果要更新的变量等于预期值,就会将要更新的变量设置为新值。如果要更新的变量不等于预期值,说明被其他线程修改了,可以再次尝试次操作直至修改成功。在硬件层面上,CPU通过总线锁定和缓存锁定来保证了以上操作的原子性。
- 是线程安全的。
- 问题: ①.CAS容易造成ABA问题。一个线程a将数值改成了b,接着又改成了a,此时CAS认为是没有变化,其实是已经变化过了,ABA问题的解决方案是加版本号,即在A变成B再变成A的时候,每一步操作加一个version,即:1A->2B->3A,这样就可以根据版本号是否有变化即可判断是否发生了ABA问题。
②.CAS造成CPU利用率增加。之前说过了CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。
③.只能保证一个共享变量的原子操作:即一个CAS操作只能保证一个共享变量的的原子性。这个问题的解决方案是利用java1.5之后提供的AtomicReference类来保证引用对象之间的原子性。可以将多个变量放进一个对象中然后进行CAS操作。
4. CAS算法的优点,和加锁的效率对比
没有锁竞争带来的系统开销,由于CAS是非阻塞的,所以避免了死锁,和加锁的方式相比有更好的性能,效率更高。
5. 原子锁(CAS)和同步锁的区别?
- 同步锁使用的是一种悲观策略,如果使用同步锁来进行并发控制,当某一个线程抢占到锁之后,那么其他线程再尝试去抢占锁时就会被挂起,当前面的线程释放锁之后,其他线程再抢占到锁后并且重新恢复到运行状态,这个过程需要花费较长的时间。
- 那么当我们使用原子锁操作时,线程1和线程2对共享变量进行并发的CAS操作,假设1成功了,2会再做多次尝试,它执行多次的所消耗的时间远远小于线程挂起到恢复所消耗的时间,因此原子锁的性能要比同步锁高很多。
6. CAS check的字段是哪里,set到哪里去?
CAS 操作包含三个操作数(V,E,N) —— V表示要更新的变量、E表示预期值,N表示新值。check要更新的变量V如果等于预期值E,就会set操作将要更新的变量V设置为新值N。
7. jdk8对乐观锁(CAS)的优化?
Java 8推出了一个新的类,LongAdder,他就是尝试使用分段CAS以及自动分段迁移的方式来大幅度提升多线程高并发执行CAS操作的性能。
- 首先有一个基值base,多线程累加数值都是对base进行累加的。
如果竞争激烈到一定程度无法对base进行累加操作时,会实施分段CAS机制,创建一个Cell数据,让大量的线程分别去对不同Cell内部的value值进行CAS累加操作,这样就把CAS计算压力分散到了不同的Cell分段数值中了,如果你要从LongAdder中获取当前累加的总值,就会把base值和所有Cell分段数值加起来返回给你。
8. 原子类底层(原子类为什么能保证线程安全)?
JUC包里有一个atomic包,里面实现了一些直接使用CAS操作的线程安全的类型。
使用volatile修饰的value来存储值,如果value被修改,每个线程都可以看到这个修改,线程对value的每一步操作都使用CAS来操作,这样就实现了线程安全。
9. JUC包里有什么?
java.util.concurrent包里有locks包,atomic包,还有一些线程安全的类,如ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentSkipListMap等,还有一些并发工具类,如CyclicBarrier、CountDownLatch。