1.Java里面的各种锁
答:其实如果按照名称来说,锁的分类为:公平锁/非公平锁、可重入锁/不可重入锁、独享锁/共享锁、互斥锁/读写锁、乐观锁/悲观锁、分段锁、偏向锁/轻量级锁/重量级锁、自旋锁:
(1)公平锁/非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。非公平锁是指多个线程获取锁的顺序不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁。
Java中的ReentrantLock,可以通过构造器指定该锁是否公平,默认是非公平的,非公平锁的优势在于吞吐量比公平锁大。而Java中的synchronized,是一种非公平锁,它并不像ReentrantLock一样,通过AQS来实现线程调度,所以没有方法让它变成公平锁。
(2)可重入锁/不可重入锁
当一个线程获得当前实例的锁lock,并且进入了方法A,该线程在方法A没有释放该锁的时候,是否可以再次进入使用该锁的方法B?不可重入锁:在方法A释放锁之前,不可以再次进入方法B;可重入锁:在方法A释放该锁之前可以再次进入方法B。synchronized和ReentrantLock都是可重入锁。
(3)独享锁/共享锁
独享锁是指该锁一次只能被一个线程持有,而共享锁是指该锁一次可以被多个线程持有。synchronized毫无疑问是独享锁,Lock类的实现ReentrantLock也是独享锁,而Lock类的另一个实现ReadWriteLock,它的读锁是共享的(可以让多个线程同时持有,提高读的效率),而它的写锁是独享的。ReadWriteLock的读写,写读,写写的过程是互斥的。
(4)互斥锁/读写锁
上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
互斥锁在Java中的具体实现就是ReentrantLock,包括
synchronized。
读写锁在Java中的具体实现就是ReadWriteLock。
(5)乐观锁/悲观锁
悲观锁认为对于同一个数据的并发操作,一定是会发生修改,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。
(6)分段锁:从锁的设计来分的,细化锁的粒度,而不是一有操作就锁住整个对象。例如在ConcurrentHashMap中。
(7)偏向锁/轻量级锁/重量级锁:这三种锁是从锁的状态来划分的,而且是针对synchronized。
在Java 5通过引入锁升级的机制来实现高效Synchronized,这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。
(8)自旋锁:自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
PS:synchronized 支持不公平锁、可重入锁,但JDK 1.6以后引入了偏向锁、自旋锁和轻量级锁和重量级锁等
2.锁的应用场景
答:
(1)偏向锁:
优点:加锁和解锁不需要额外的开销,和执行非同步方法相比仅存在纳秒级的差距。
缺点:如果线程间存在锁竞争,会带来额外的锁撤销的消耗
适用场景:适用于只有一个线程访问同步块场景
(2)轻量级锁:
优点:竞争的线程不会阻塞,提高了程序的响应速度。
缺点:如果始终得不到锁竞争的线程,使用自旋会消耗CPU
适用场景:追求响应时间,同步块执行速度非常快
(3)重量级锁:
优点:线程竞争不使用自旋,不会消耗CPU
缺点:线程阻塞,响应时间缓慢
适用场景:追求吞吐量。同步块执行速度较长