可重入性/公平锁/非公平锁
可重入性
所谓的可重入性,就是可以支持一个线程对锁的重复获取,原生的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支持多个condition
7、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 }
运行结果:
代码分析:
三个线程分别循环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方法的线程,所以也就可以实现唤醒指定类的线程