之前面试被问到JAVA中锁的问题,这块没有花时间去学习,于时查阅了一些资料整理一下
平时项目中多线程同步一般使用synchronized关键字,了解这个关键字的用法属于初级技术。
如果面试官自身懂技术便会追问不同锁类型之间的区别,如果回答不清楚很难被定义为高级开发人员。
几种主要的锁的类型:
1. 悲观锁/乐观锁
2. 可重入锁/不可重入锁
3. 无锁/偏向锁/自旋锁/轻量锁/重量锁
4. 独享锁/共享锁
5. 公平锁/非公平锁
一. 悲观锁/乐观锁
首先悲观锁和乐观锁属于一种编程的思想,悲观锁即开发人员使用了 synchronized或者Lock类的实例去实现线程同步,即开发人员认为当某个线程操作同步代码中的数据时,会有其他线程同时来操作这部分数据,因此在获取数据之前会先加锁,确保数据不会被修改;
而乐观锁在JAVA编程中的表现为无锁编程,即开发人员认为某个线程获取数据时,不会有其他线程来修改正在访问的数据,只是在更新数据的时候去判断之前是否有其他线程更改过数据,如果没有其他线程更新过数据,当前线程执行更新数据操作,否则会做一些异常或其他处理。
悲观锁适合写操作多的场景,防止获取数据时其他线程正在修改该数据而引发错误;
乐观锁适合读操作多的场景,无锁编程可以提高读操作的性能。乐观锁结合基于CAS算法的类使用,例如使用AtomicInteger类,在数据自增的时候依靠对象内部的CAS算法保证修改数据之前没有其他线程修改过数据。
二. 可重入锁/ 不可重入锁
JAVA中的synchronized关键字和 ReentrantLock类的实例都是可重入锁,即在加锁的方法中可以执行其他加锁的方法(两个锁是同一个对象或class),不会因为外层的锁没有释放而阻塞。
NonReentrantLock类是不可重入锁。
一般开发中常用的是可重入锁,暂时没有接触到必须使用非可重复锁的场景
三. 无锁/偏向锁/自旋锁/轻量锁/重量锁
自旋锁 某个线程已经占有了锁,其他线程来访问相关数据时不立即阻塞,而是处于自旋,循环等待的状态。
无锁即不使用锁,个人感觉和乐观锁概念类似;无锁的好处是数据处理的效率高;但是如果存在多线程对数据写操作的话就需要用到synchronized关键字;后面3种状态在使用synchronized关键字时会存在;
偏向锁 如果只有一个线程在访问同步代码块,就会产生偏向锁,这个线程获取锁的开销会比较小。
轻量级锁 当某个加锁的对象已经被某个线程占用,当前处于偏向锁的状态;如果此时其他线程来访问该数据,偏向锁会升级为轻量级锁,即其他线程会自旋等待锁的释放,而不是直接进入阻塞状态,因为可能过很短的时间即可获得锁,而如果直接进入阻塞状态再被唤醒的开销会比较大。
重量级锁 依靠系统底层Monitor 同步机制,当某个加锁的对象已经被某个线程占用,其他线程再次访问时会立即进入阻塞状态。
synchronized关键字在JDK 6之前只有重量级锁以中状态,之后引入了偏向锁和轻量级锁,3种锁的状态在合适的时机会自动切换(有些说法时只能向上切换,待查证);这样可以在保证稳定性的前提下提高性能。
四. 独享锁/共享锁
独享锁 该锁一次只能被一个线程锁持有;例如synchronized关键字和Lock的实现类就是独享锁。
共享锁 数据A被线程T加了共享锁,其他线程可以再对数据A加共享锁,持有共享锁的线程只能读数据而不能修改数据。
五. 公平锁/ 非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。