Java线程-锁的分类(四)
2018-07-22 本文已影响65人
骑着乌龟去看海
一、前言
Java中的锁有各种特性,比如可重入与不可重入,公平与非公平等,也可以根据这些特性进行分类。
二、可重入锁
- 当某个线程请求一个由其他线程持有的锁的时,发出请求的的线程就会阻塞。但当一个线程再次请求自己持有的对象锁时,如果能请求成功,说明这个锁是可重入的。
- 拿synchronized来说,synchronized的锁是原子性的,也是可重入的,因此一个线程调用synchronized方法的同时在其方法体内部调用该对象的另一个synchronized方法将不会进入阻塞状态,也就是说一个线程得到对象锁后再次请求该对象锁是允许的,这就是synchronized的可重入性。
看一段代码:
public class ThreadTest extends SuperClass {
public synchronized void test(Thread thread) {
System.out.println(thread.getName());
super.test();
}
}
class SuperClass {
public synchronized void test() {
System.out.println(Thread.currentThread().getName());
}
}
子类重写了父类的test方法,然后调用父类的方法,此时如果没有可重入的锁,那么这段代码将会产生死锁。因为两个方法都是synchronized方法,因此每个test方法再执行前都会获取SuperClass对象锁,然后如果该锁不是可重入的,那么在调用super.test方法时将无法获得SuperClass对象上的锁,因为这个锁已经被持有,从而线程将永远停顿下去,等待一个永远也无法获得的锁。
- 可重入锁的一种实现方式是为每个锁关联一个计数值和一个所有者线程。当计数器为0时,这个锁就被认为是没有被任何线程持有,当线程请求一个未被持有的锁时,JVM将几下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,计数值递增,而当线程退出同步代码块时,计数器会相应的递减,当计数器为0时,该锁被释放。
- 可重入锁,有时也被称为递归锁,也就是说外层代码获取该锁之后,内层递归函数仍然可以获得该锁,JAVA中,
ReentrantLock
和synchronized
都是 可重入锁;- 可重入锁的最大作用就是可以避免死锁;
二、可中断锁
- 可中断锁,顾名思义就是可以中断阻塞中的锁,Java中的synchronized由于不可中断,不是可中断锁,而Lock则是可中断锁。
- 通俗的来说,如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
三、读写锁
就是说对资源的访问操作分为读锁和写锁,分开来进行。就是因为有了读写锁,才使得多个线程之间的读操作不会发生冲突,对应的接口是ReadWriteLock,该接口对应的实现类是ReentrantReadWriteLock,可以通过对应的readLock()方法获取读锁,通过writeLock()方法获取写锁,后续我们会学习到。
四、公平锁
公平锁是说根据请求锁的顺序来获取锁,比如同时有多个线程在等待获取锁的时候,最先请求的线程会获取该锁,这种就是公平锁。非公平锁就无法保证这点了,但需要注意下:
- Java中的synchronized是一种非公平锁,无法保证最先请求的线程先获取到锁,而ReentrantLock和ReentrantReadWriteLock,默认情况下是非公平锁,但可以设置为公平锁,后续我们学习到这里的时候再说。
- 对ReentrantLock来说,即使是公平锁,对于可轮询的tryLock仍然有可能会出现“插队”的现象,也就是不按线程请求顺序的情况。
- 公平锁的实现需要通过一个队列来维护线程请求的顺序性,并且在进行等待和唤醒线程时存在一定的性能开销,而非公平性则不用考虑这些,所以大多数情况下,非公平锁的性能要高于公平锁的性能。但当持有锁的实际相对较长,或者请求锁的平均时间间隔较长的时候,这时候可以使用公平锁。
本文参考自:
海子-Java并发编程-Lock
《Java并发编程实战》