Java并发编程-ReentrantLock样例详细解析

Java并发编程-ReentrantLock样例详细解析_第1张图片
正文
  ReentrantLock是Java并发包中提供的一个可重入的互斥锁。ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性。只不过相比原生的Synchronized,ReentrantLock增加了一些高级的扩展功能,比如它可以实现公平锁,同时也可以绑定多个Conditon。可重入性/公平锁/非公平锁可重入性
所谓的可重入性,就是可以支持一个线程对锁的重复获取,原生的synchronized就具有可重入性,一个用synchronized修饰的递归方法,当线程在执行期间,它是可以反复获取到锁的,而不会出现自己把自己锁死的情况。ReentrantLock也是如此,在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
公平锁/非公平锁
所谓公平锁,顾名思义,意指锁的获取策略相对公平,当多个线程在获取同一个锁时,必须按照锁的申请时间来依次获得锁,排排队,不能插队;非公平锁则不同,当锁被释放时,等待中的线程均有机会获得锁。synchronized是非公平锁,ReentrantLock默认也是非公平的,但是可以通过带boolean参数的构造方法指定使用公平锁,但非公平锁的性能一般要优于公平锁。  synchronized是Java原生的互斥同步锁,使用方便,对于synchronized修饰的方法或同步块,无需再显式释放锁。
而ReentrantLock做为API层面的互斥锁,需要显式地去加锁解锁。采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
class X {
private final ReentrantLock lock = new ReentrantLock();
// …
public void m() {
lock.lock(); // 加锁
try {
// … 函数主题
}
finally {
lock.unlock() //解锁
}
}
}

源码分析  
接下来我们从源码角度来看看ReentrantLock的实现原理,它是如何保证可重入性,又是如何实现公平锁的。
1、无参构造器(默认为非公平锁)public ReentrantLock() {
sync = new NonfairSync();//默认是非公平的
}sync是ReentrantLock内部实现的一个同步组件,它是Reentrantlock的一个静态内部类,继承于AQS。
2、带布尔值的构造器(是否公平)public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() :
new NonfairSync();//fair为true,公平锁;反之,非公平锁
}此处可以指定是否采用公平锁,FailSync和NonFailSync亦为Reentrantlock的静态内部类,都继承于Sync。
3、lock()public void lock() {
sync.lock();//代理到Sync的lock方法上
}Sync的lock方法是抽象的,实际的lock会代理到FairSync或是NonFairSync上(根据用户的选择来决定,公平锁还是非公平锁)
4、unlock()public void unlock() {
sync.release(1);//释放锁
}释放锁,调用sync的release方法。
5、tryLock()
Lock lock = …;if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){

 }
 finally{
     lock.unlock();   //释放锁
          } 

}else {
//如果不能获取锁,则直接做其他事情
}
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false。
6、newCondition()public Condition newCondition() {
return sync.newCondition();
}获取一个conditon,ReentrantLock支持多个Condition7、await()
public class MyService {
private Lock lock = new ReentrantLock();
private Condition condition=lock.newCondition();
public void testMethod() {
try {
lock.lock();
System.out.println(“开始wait”);
condition.await();
for (int i = 0; i < 5; i++) {
System.out.println(“ThreadName=” + Thread.currentThread().getName() + (" " + (i + 1)));
}
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
finally
{
lock.unlock();
}
}

}
通过创建Condition对象来使线程wait,必须先执行lock.lock方法获得锁8、signal()
public void signal() {
try {
lock.lock();
condition.signal();
} finally {
lock.unlock();
}
}
condition对象的signal方法可以唤醒wait线程
9、创建多个condition对象  
一个condition对象的signal(signalAll)方法和该对象的await方法是一一对应的,也就是一个condition对象的signal(signalAll)方法不能唤醒其他condition对象的await方法ABC循环打印20遍
1 package main.java.Juc;
2
3 import java.util.concurrent.locks.Condition;
4 import java.util.concurrent.locks.Lock;
5 import java.util.concurrent.locks.ReentrantLock;
6
7 /*
8 * 编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。
9 * 如:ABCABCABC…… 依次递归
10 /
11 public class TestABCAlternate {
12
13 public static void main(String[] args) {
14 AlternateDemo ad = new AlternateDemo();
15
16 new Thread(new Runnable() {
17 @Override
18 public void run() {
19 for (int i = 1; i <= 20; i++) {
20 ad.loopA(i);
21 }
22 }
23 }, “A”).start();
24
25 new Thread(new Runnable() {
26 @Override
27 public void run() {
28 for (int i = 1; i <= 20; i++) {
29 ad.loopB(i);
30 }
31 }
32 }, “B”).start();
33
34 new Thread(new Runnable() {
35 @Override
36 public void run() {
37 for (int i = 1; i <= 20; i++) {
38 ad.loopC(i);
39 System.out.println("-----------------------------------");
40 }
41 }
42 }, “C”).start();
43 }
44
45 }
46
47 class AlternateDemo{
48
49 private int number = 1; //当前正在执行线程的标记
50
51 private Lock lock = new ReentrantLock();
52 private Condition condition1 = lock.newCondition();
53 private Condition condition2 = lock.newCondition();
54 private Condition condition3 = lock.newCondition();
55
56 /
*
57 * @param totalLoop : 循环第几轮
58 */
59 public void loopA(int totalLoop){
60 lock.lock();
61 try {
62 //1. 判断
63 if(number != 1){
64 condition1.await();
65 }
66 //2. 打印
67 for (int i = 1; i <= 1; i++) {
68 System.out.println(Thread.currentThread().getName() + “\t” + i + “\t” + totalLoop);
69 }
70 //3. 唤醒
71 number = 2;
72 condition2.signal();
73 } catch (Exception e) {
74 e.printStackTrace();
75 } finally {
76 lock.unlock();
77 }
78 }
79
80 public void loopB(int totalLoop){
81 lock.lock();
82 try {
83 //1. 判断
84 if(number != 2){
85 condition2.await();
86 }
87 //2. 打印
88 for (int i = 1; i <= 1; i++) {
89 System.out.println(Thread.currentThread().getName() + “\t” + i + “\t” + totalLoop);
90 }
91 //3. 唤醒
92 number = 3;
93 condition3.signal();
94 } catch (Exception e) {
95 e.printStackTrace();
96 } finally {
97 lock.unlock();
98 }
99 }
100
101 public void loopC(int totalLoop){
102 lock.lock();
103 try {
104 //1. 判断
105 if(number != 3){
106 condition3.await();
107 }
108 //2. 打印
109 for (int i = 1; i <= 1; i++) {
110 System.out.println(Thread.currentThread().getName() + “\t” + i + “\t” + totalLoop);
111 }
112 //3. 唤醒
113 number = 1;
114 condition1.signal();
115 } catch (Exception e) {
116 e.printStackTrace();
117 } finally {
118 lock.unlock();
119 }
120 }
121
122 }
运行结果:
Java并发编程-ReentrantLock样例详细解析_第2张图片
代码分析:  
三个线程分别循环20次调用loopA、loopB、loopC打印,但是不确定是哪个方法先被调用到,如果是loopB先调用,则loopB方法先获取到锁,loopA和loopC等待锁,此时线程执行标记number=1,代码84行处为true,则condition2.await();如果需要唤醒此线程,则需要用condition2来唤醒,此时线程交出锁;  如果loopA获取了锁,loopB和loopC等待锁,此时线程执行标记number=1,代码63行处为false,则执行67行打印,打印完则用condition2.signal()唤醒打印loopB的线程,接着loopB的线程去打印B,线程loopB打印完毕去唤醒打印loopC的线程,打印完loopC再唤醒loopA,如此循环20次。
总结
1、Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3、Lock类可以创建Condition对象,Condition对象用来是线程等待和唤醒线程,需要注意的是Condition对象的唤醒的是用同一个Condition执行await方法的线程,所以也就可以实现唤醒指定类的线程.
文章来自:https://www.itjmd.com/news/show-4257.html

你可能感兴趣的:(Java)