【并发编程】简单易懂的了解ReentrantLock锁的各种机制

ReentrantLock是什么?

  • ReentrantLock是一种基于AQS框架的应用实现。
  • 是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。

ReentrantLock与synchronized的区别

  • synchronized是JVM层次的锁实现,ReentrantLock是JDK层次的锁实现;
  • synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过ReentrantLock#isLocked判断;
  • synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;
  • synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以被中断的;
  • 在发生异常时synchronized会自动释放锁,而ReentrantLock需要开发者在finally块中显示释放锁;
  • ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活;
  • synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁,而ReentrantLock对于已经在等待的线程是先来的线程先获得锁;

ReentrantLock的使用方式

  • 使用的方式
//参数默认false,不公平锁
ReentrantLock lock = new ReentrantLock();
//公平锁 
ReentrantLock lock = new ReentrantLock(true); 

// 加锁
lock.lock();
try {
    //临界区
} finally {
    // 解锁
    lock.unlock();
}
  • 使用实例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Tset1 {

	// 定义数据
	private static int sum = 0;

	// 定义自己的锁
	private static Lock lock = new ReentrantLock();

	public static void main(String[] args) throws InterruptedException {

		// 循环3吃,开启三个线程
		for (int i = 0; i < 3; i++) {
			Thread thread = new Thread(() -> {
				// 加锁
				lock.lock();
				try {
					// 不加锁这里执行完小于30000
					for (int j = 0; j < 10000; j++) {
						sum++;
					}
				} finally {
					// 解锁
					lock.unlock();
				}
			});
			thread.start();
		}

		// 等待线程执行完
		Thread.sleep(2000);

		// 打印最后的结果:30000
		System.out.println(sum);
	}
}

ReentrantLock的可重入机制

  • 减少加锁解锁的过程,然后去提高性能
import java.util.concurrent.locks.ReentrantLock;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Tset2 {

	public static ReentrantLock lock = new ReentrantLock();

	public static void main(String[] args) {
		// 第一个线程中调用第二个线程、第二个线程中调用第三个线程
		method1();
	}

	public static void method1() {
		lock.lock();
		try {
			log.debug("执行 method1");
			method2();
		} finally {
			lock.unlock();
		}
	}

	public static void method2() {
		lock.lock();
		try {
			log.debug("执行 method2");
			method3();
		} finally {
			lock.unlock();
		}
	}

	public static void method3() {
		lock.lock();
		try {
			log.debug("执行 method3");
		} finally {
			lock.unlock();
		}
	}
}

  • 执行结果

可重入的执行结果.png

ReentrantLock的可中断机制(立即中断)

  • 应用场景:一个类只需要一个线程去初始化,其他线程初始化完成后当前线程不需要继续处理。
import java.util.concurrent.locks.ReentrantLock;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Tset3 {

	public static void main(String[] args) {
		ReentrantLock lock = new ReentrantLock();

		Thread t1 = new Thread(() -> {

			log.debug("t1启动...");
			// 注意: 即使是设置的公平锁,此方法也会立即返回获取锁成功或失败,公平策略不生效
			if (!lock.tryLock()) {
				log.debug("t1获取锁失败,立即返回false");
				return;
			}
			try {
				log.debug("t1获得了锁");
			} finally {
				lock.unlock();
			}

		}, "t1");

		lock.lock();
		try {
			log.debug("main线程获得了锁");
			t1.start();
			// 先让线程t1执行
			try {
				// 模拟业务
				while (true) {

				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		} finally {
			lock.unlock();
		}

	}
}

  • 执行结果

可中断机制.png

ReentrantLock的锁超时超时失败机制

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Tset4 {

	public static void main(String[] args) {
		ReentrantLock lock = new ReentrantLock();
		Thread t1 = new Thread(() -> {
			log.debug("t1启动...");
			// 超时
			try {
				if (!lock.tryLock(1, TimeUnit.SECONDS)) {
					log.debug("等待 1s 后获取锁失败,返回");
					return;
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
				return;
			}
			try {
				log.debug("t1获得了锁");
			} finally {
				lock.unlock();
			}

		}, "t1");

		lock.lock();
		try {
			log.debug("main线程获得了锁");
			t1.start();
			// 先让线程t1执行
			try {
				// 模拟业务
				while (true) {

				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		} finally {
			lock.unlock();
		}
	}
}

  • 执行结果

锁超时超时失败机制.png

ReentrantLock的公平锁方式机制

  • new ReentrantLock(true);的时候穿一个true就是公平锁,不传或者传一个false就是非公平锁!
import java.util.concurrent.locks.ReentrantLock;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Tset5 {

	public static void main(String[] args) throws InterruptedException {
		ReentrantLock lock = new ReentrantLock(true); // 公平锁 

		for (int i = 0; i < 500; i++) {
			new Thread(() -> {
				lock.lock();
				try {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					log.debug(Thread.currentThread().getName() + " running...");
				} finally {
					lock.unlock();
				}
			}, "t" + i).start();
		}
		
		// 1s 之后去争抢锁
		Thread.sleep(1000);

		for (int i = 0; i < 500; i++) {
			new Thread(() -> {
				lock.lock();
				try {
					log.debug(Thread.currentThread().getName() + " running...");
				} finally {
					lock.unlock();
				}
			}, "强行插入" + i).start();
		}
	}
}

  • 公平锁运行结果:按照进入的顺序执行,正常顺序日志,无突出的重点,不截图了。

  • 非公平锁运行(定义ReentrantLock的时候不传值)结果:出现插队的线程!

非公平锁运行出现插队的情况.png

ReentrantLock的阻塞唤醒机制

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Tset6 {
	private static ReentrantLock lock = new ReentrantLock();
	private static Condition cigCon = lock.newCondition();

	private static boolean hashcig = false;

	public static void main(String[] args) {
		
		// 定义线程需要被唤醒的线程
		new Thread(() -> {
			lock.lock();
			log.debug("需要被唤醒的线程加锁");
			try {
				while (!hashcig) {
					try {
						log.debug("需要被唤醒的线程等待!");
						cigCon.await();

					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				log.debug("需要被唤醒的线程跳出循环!");
			} finally {
				lock.unlock();
			}
		}).start();

		// 准备要唤醒的线程
		new Thread(() -> {
			lock.lock();
			try {
				// 设置条件变了
				hashcig = true;
				// 唤醒等待线程
				cigCon.signal();
			} finally {
				lock.unlock();
			}

		}, "t1").start();
	}
}

  • 运行结果

阻塞唤醒机制.png

结束语

  • 获取更多有价值的文章,让我们一起成为架构师!
  • 关注公众号,可以让你对MySQL有非常深入的了解
  • 关注公众号,每天持续高效的了解并发编程!
  • 关注公众号,后续持续高效的了解spring源码!
  • 这个公众号,无广告!!!每日更新!!!
    作者公众号.jpg
posted @ 2022-01-30 15:01  程序java圈  阅读(291)  评论(0编辑  收藏  举报