可重入锁ReentrantLock的使用

这两天抽了点时间看了关于ReentrantLock的博客,上一篇的就是其中一篇。自己看完也来复习一下先。

ReentrantLock是一个可重入的互斥(独占)锁,又称为“独占锁”。
ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSynchronized,是显示锁的关键)来实现锁的获取与释放。其可以完全替代synchronized关键字。JDK5.0早期,其性能远好于synchronized关键字,但从JDK6.0开始,JDK对synchronized做了大量的优化,使得两者差距并不大。
独占”:就是在同一时刻只能有一个线程获取到锁,而其它获取锁的线程只能处于同步队列中的等待,只有获取锁的线程释放了锁,后续的线程才能获取锁。
可重入”:就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁, 是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞。
该锁还支持获取锁时的公平和非公平选择。“公平”是指不同的线程获取锁的机制是公平的,而“不公平”是指不同的线程获取锁的机制是非公平的。默认的锁是非公平的。
还有最后我们需要注意一下的是:最后一定要释放锁,不然会造成锁永远无法释放,其他线程永远进不来的结果。

1、下面我们先看一下简单的使用例子:
public class UseReentrantLock {
	private Lock lock = new ReentrantLock();
	
	public void methos1(){
		try{  //一般都是配合try-catch-finally使用
			lock.lock();
			System.out.println("线程:"+Thread.currentThread().getName()+"获取了锁进入方法1");
			Thread.sleep(1000);
		}catch(Exception e){
			e.printStackTrace();
		}finally{ //在最后释放锁
			lock.unlock();
			System.out.println("线程:"+Thread.currentThread().getName()+"释放锁退出方法1");
		}
	}
	
	public void methos2(){
		try{  //一般都是配合try-catch-finally使用
			lock.lock();
			System.out.println("线程:"+Thread.currentThread().getName()+"获取了锁进入方法2");
			Thread.sleep(1000);
		}catch(Exception e){
			e.printStackTrace();
		}finally{ //在最后释放锁
			lock.unlock();
			System.out.println("线程:"+Thread.currentThread().getName()+"释放锁退出方法2");
		}
	}
	
	public static void main(String[] args) {
		UseReentrantLock ur = new UseReentrantLock();
		Thread t1 = new Thread(new Runnable(){

			@Override
			public void run() {
				ur.methos1();
				ur.methos2();
			}
			
		},"t1");
		
		t1.start();
	}
}
执行结果:线程1执行完方法1释放锁后确实可以继续获得锁执行方法2,并没有被锁阻塞,这大概就是可重入的意思吧。
线程:t1获取了锁进入方法1
线程:t1释放锁退出方法1
线程:t1获取了锁进入方法2
线程:t1释放锁退出方法2

2、还记得我们在在使用synchronized的时候,如果需要多线程间进行协作工作则需要使用超类Object的wait()和notify()、notifyAll()方法进行配合工作。
那么同样的,我们在使用Lock的时候,可以使用一个新的等待/通知的类,它就是Conditon。这个Condition一定是针对具体某一把锁的,也就是在只有锁的基础上才会产生Condition。
下面看看例子:
public class UseReentrantLockCondition {
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public void method1(){
		try{
			lock.lock();
			System.out.println("线程"+Thread.currentThread().getName()+":获取锁进入方法1");
			Thread.sleep(3000);
			System.out.println("线程"+Thread.currentThread().getName()+":方法1进行等待状态,释放锁");
			condition.await();  //效果等同于Object的await()方法,会释放锁
			System.out.println("线程"+Thread.currentThread().getName()+":重新获取锁继续执行方法1");
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public void method2(){
		try{
			lock.lock();
			System.out.println("线程"+Thread.currentThread().getName()+"获取锁进入方法2");
			Thread.sleep(3000);
			System.out.println("线程"+Thread.currentThread().getName()+":方法2发出唤醒");
			condition.signal();  //效果等同于Object的notify()方法,不会释放锁
			System.out.println("线程"+Thread.currentThread().getName()+":方法2执行结束");
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		UseReentrantLockCondition ur = new UseReentrantLockCondition();
		Thread t1 = new Thread(new Runnable(){

			@Override
			public void run() {
				ur.method1();
			}
			
		},"t1");
		Thread t2 = new Thread(new Runnable(){

			@Override
			public void run() {
				ur.method2();
			}
			
		},"t2");
		
		t1.start();
		try {
			Thread.sleep(1000);  //保证线程1先执行
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
	}
}
执行结果:可以看到,当线程1执行的方法1调用await方法后就释放锁了,然后线程2就获得了锁执行方法2,当方法2调用了唤醒操作signal而且执行完毕后(唤醒操作不释放锁),线程1再获得锁得以继续执行方法1。
线程t1:获取锁进入方法1
线程t1:方法1进行等待状态,释放锁
线程t2获取锁进入方法2
线程t2:方法2发出唤醒
线程t2:方法2执行结束
线程t1:重新获取锁继续执行方法1

3、上面是一个Condition,其实一个Lock对象是可以产生多个Condition进行多线程间的交互的,所以是非常的灵活,可以使得部分需要唤醒的线程被唤醒,而其他线程则继续等待通知。
下面直接上例子:
public class UseReentrantLockConditions {
	private Lock lock = new ReentrantLock();
	private Condition c1 = lock.newCondition();
	private Condition c2 = lock.newCondition();
	
	public void method1(){
		try{
			lock.lock();
			System.out.println("线程"+Thread.currentThread().getName()+"进入方法1,等待");
			c1.await(); //调用c1的await方法
			System.out.println("线程"+Thread.currentThread().getName()+"继续执行方法1");
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public void method2(){
		try{
			lock.lock();
			System.out.println("线程"+Thread.currentThread().getName()+"进入方法2,等待");
			c1.await(); //调用c1的await方法
			System.out.println("线程"+Thread.currentThread().getName()+"继续执行方法2");
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public void method3(){
		try{
			lock.lock();
			System.out.println("线程"+Thread.currentThread().getName()+"进入方法3,等待");
			c2.await(); //调用c2的await方法
			System.out.println("线程"+Thread.currentThread().getName()+"继续执行方法3");
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public void method4(){
		try{
			lock.lock();
			System.out.println("线程"+Thread.currentThread().getName()+"进入方法4,执行c1的唤醒操作");
			c1.signalAll();; //调用c1的signalAll方法,唤醒全部线程
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public void method5(){
		try{
			lock.lock();
			System.out.println("线程"+Thread.currentThread().getName()+"进入方法5,执行c2的唤醒操作");
			c2.signal(); //执行c2的signal
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public static void main(String[] args) {
		UseReentrantLockConditions ur = new UseReentrantLockConditions();
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				ur.method1();
			}
		},"t1");
		Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				ur.method2();
			}
		},"t2");
		Thread t3 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				ur.method3();
			}
		},"t3");
		Thread t4 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				ur.method4();
			}
		},"t4");
		Thread t5 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				ur.method5();
			}
		},"t5");
		
		t1.start();
		t2.start();
		t3.start();
		
		try {
			Thread.sleep(2000);  //保证线程123先执行
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t4.start();
		try {
			Thread.sleep(2000);  //让两个唤醒操作效果更明显
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t5.start();
		
	}
	
}
执行结果:可看到当线程4执行c1的唤醒操作signalAll()后,线程1和线程2都可以继续执行方法了。而当线程5执行c2的唤醒操作signal()后,线程3可以继续执行方法。
线程t1进入方法1,等待
线程t2进入方法2,等待
线程t3进入方法3,等待
线程t4进入方法4,执行c1的唤醒操作
线程t1继续执行方法1
线程t2继续执行方法2
线程t5进入方法5,执行c2的唤醒操作
线程t3继续执行方法3

小结:
其实ReentrantLock还有很多方法,其中下面还有三个需要注意一下,但是就不做例子了。
1、lockInterruptibly()方法:获得锁,但是优先响应中断。就是如果线程调用了interrupt()这个中断方法,那么不会去获得锁。
2、tryLock():尝试获得锁,如果成功立即返回true,反之失败就返回false。该方法不会进行等待,立即返回值。
3、tryLock(long time,TimeUnit unit):在给定时间内尝试获得锁。

猜你喜欢

转载自blog.csdn.net/howinfun/article/details/80858396