JAVA多线程 重入锁和读写锁

在java多线程中,我们真的可以使用synchronized关键字来实现线程间的同步互斥工作,那么其实还有一个更优秀的机制去完成这个“同步互斥”工作,他就是Lock对象,重入锁和读写锁。他们具有比synchronized更为强大的功能,并且有嗅探锁定、多路分支等功能。

一、重入锁

在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果。
ReentrantLock (重入锁)示例:

/**
 * 重入锁
 * @author yanghz
 * @createDate 2018年11月19日
 */
public class UseReentrantLock {
	
	private Lock lock=new ReentrantLock();
	
	public void method1(){
		try{
			lock.lock();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"进入method1");
			Thread.sleep(1000);
			System.out.println("当前线程:"+Thread.currentThread().getName()+"退出method1");
			Thread.sleep(1000);
		}catch(Exception e){			
		}finally{
			//释放锁
			lock.unlock();
		}		
	}
	public void method2(){
		try{
			lock.lock();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"进入method2");
			Thread.sleep(2000);
			System.out.println("当前线程:"+Thread.currentThread().getName()+"退出method2");
			Thread.sleep(1000);
		}catch(Exception e){			
		}finally{
			//释放锁
			lock.unlock();
		}		
	}
	public static void main(String[] args) {
		final UseReentrantLock ur=new UseReentrantLock();
		Thread t1=new Thread(new Runnable() {
			
			@Override
			public void run() {
				ur.method1();
				ur.method2();
			}
		},"t1");
		t1.start();
	}	
}

运行结果:
当前线程:t1进入method1
当前线程:t1退出method1
当前线程:t1进入method2
当前线程:t1退出method2

Condition类的使用:


/**
 * Lock的通知和等待用法
 * @author yanghz
 * @createDate 2018年11月19日
 */
public class UseCondition {
	
	private Lock lock=new ReentrantLock();
	private Condition condition=lock.newCondition();
	
	public void method1(){		
		try {
			lock.lock();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"进入等待状态。。。。");
			Thread.sleep(3000);
			System.out.println("当前线程:"+Thread.currentThread().getName()+"释放锁。。。。");
			condition.await();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"继续执行。。。。");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public void method2(){		
		try {
			lock.lock();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"进入等待状态。。。。");
			Thread.sleep(3000);
			System.out.println("当前线程:"+Thread.currentThread().getName()+"发起唤醒锁。。。。");
			condition.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		final UseCondition uc=new UseCondition();
		Thread t1=new Thread(new Runnable() {
			public void run() {
				uc.method1();
			}
		},"t1");
		
		Thread t2=new Thread(new Runnable() {
			public void run() {
				uc.method2();
			}
		},"t2");
		
		t1.start();
		t2.start();
	}

}

输出结果:

当前线程:t1进入等待状态。。。。
当前线程:t1释放锁。。。。
当前线程:t2进入等待状态。。。。
当前线程:t2发起唤醒锁。。。。
当前线程:t1继续执行。。。。

多Condition

/**
 * 多Condition
 * @author yanghz
 * @createDate 2018年11月19日
 */
public class UseManyCondition {

	private Lock lock = new ReentrantLock();
	private Condition c1=lock.newCondition();
	private Condition c2=lock.newCondition();
	
	public void m1(){
		try{
			lock.lock();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"进入方法m1等待...");
			c1.await();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"方法m1继续执行...");
		}catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void m2(){
		try{
			lock.lock();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"进入方法m2等待...");
			c1.await();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"方法m2继续执行...");
		}catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void m3(){
		try{
			lock.lock();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"进入方法m3等待...");
			c2.await();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"方法m3继续执行...");
		}catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void m4(){
		try{
			lock.lock();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"唤醒...");
			c1.signalAll();
		}finally{
			lock.unlock();
		}
	}
	public void m5(){
		try{
			lock.lock();
			System.out.println("当前线程:"+Thread.currentThread().getName()+"唤醒...");
			c2.signal();
		}finally{
			lock.unlock();
		}
	}
	public static void main(String[] args) {
		final UseManyCondition umc=new UseManyCondition();
		Thread t1=new Thread(new Runnable() {			
			@Override
			public void run() {
				umc.m1();
			}
		},"t1");
		Thread t2=new Thread(new Runnable() {			
			@Override
			public void run() {
				umc.m2();
			}
		},"t2");
		Thread t3=new Thread(new Runnable() {			
			@Override
			public void run() {
				umc.m3();
			}
		},"t3");
		Thread t4=new Thread(new Runnable() {			
			@Override
			public void run() {
				umc.m4();
			}
		},"t4");
		Thread t5=new Thread(new Runnable() {			
			@Override
			public void run() {
				umc.m5();
			}
		},"t5");
		t1.start();
		t2.start();
		t3.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t4.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t5.start();
	}
}

输出结果:

当前线程:t2进入方法m2等待…
当前线程:t1进入方法m1等待…
当前线程:t3进入方法m3等待…
当前线程:t4唤醒…
当前线程:t2方法m2继续执行…
当前线程:t1方法m1继续执行…
当前线程:t5唤醒…
当前线程:t3方法m3继续执行…

总结:当m1,m2,m3同时等待的时候,m4唤醒的只有m1,m2。m5唤醒m3
await()相当于synchronized里面使用的wait();
signal()相当于synchronized里面使用的notify();

Lock/Condition其他方法和用法:

公平锁和非公平锁:公平锁浪费资源,非公平锁不需要维护是否公平所以性能高于公平锁。
Lock lock=new ReentrantLock(boolean isFair);
Lock用法:
tryLock():尝试获得锁,获得结果用true/false返回。true表示获得,false表示锁被占用。
tryLock():在给定的世界内尝试获得锁,获得结果用true/false返回
isFair():是否是公平锁。
isLocked():是否锁定。
getHoldCount():查询当前线程保持此锁的个数,也就是调用lock()次数。
lockInterruptibly():有限响应中断的锁。
getQueueLength():返回正在等待获取此锁的线程数。
getWaitQueueLength():返回等待与锁定相关的给定条件Condition的线程数。
hasQueuedThread(Thread thread):查询指定的线程是否正在等待此锁。
hasQueuedThreads():查询是否有线程正在等待此锁。
hasWaiters():查询是否有线程正在等待与此锁定有关的Condition条件。

二、ReentrantReadWriteLock读写锁

读写锁ReentrantReadWriteLock,其核心就是实现读写分离的锁,在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁。
synchronized、ReentrantLock,同一时间内,只能有一个线程进行访问被锁定的代码,那么读写锁则不同,其本质是分成两个锁,即读锁、写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个的顺序访问。
口诀:读读共享,写写互斥,读写互斥。

示例:

/**
 * 读写锁
 * @author yanghz
 * @createDate 2018年11月19日
 */
public class UseReentrantReadWriteLock {

	private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	private ReadLock readLock = readWriteLock.readLock();
	private WriteLock writeLock = readWriteLock.writeLock();

	public void read() {
		try {
			readLock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入。。。");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出。。。");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			readLock.unlock();
		}
	}

	public void write() {
		try {
			writeLock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入。。。");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出。。。");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			writeLock.unlock();
		}
	}

	public static void main(String[] args) {
		final UseReentrantReadWriteLock urr = new UseReentrantReadWriteLock();
		Thread t1 = new Thread(new Runnable() {

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

			@Override
			public void run() {
				urr.read();
			}
		}, "t2");
		Thread t3 = new Thread(new Runnable() {

			@Override
			public void run() {
				urr.write();
			}
		}, "t3");

		Thread t4 = new Thread(new Runnable() {

			@Override
			public void run() {
				urr.write();
			}
		}, "t4");
		
//		t1.start();//R
//		t2.start();//R
		t3.start();//w
		t4.start();//w
		//RR
//		当前线程:t1进入。。。
//		当前线程:t2进入。。。
//		当前线程:t1退出。。。
//		当前线程:t2退出。。。
		//RW
//		当前线程:t3进入。。。
//		当前线程:t3退出。。。
//		当前线程:t2进入。。。
//		当前线程:t2退出。。。
		//WW
//		当前线程:t4进入。。。
//		当前线程:t4退出。。。
//		当前线程:t3进入。。。
//		当前线程:t3退出。。。
	}
}

总结:分别对应RR、RW、WW执行输出的结果可以对应上口诀

猜你喜欢

转载自blog.csdn.net/yanghzaini/article/details/84262337