Java中的可重入锁
在前面ConcurrentHashMap的实现原理与使用(二)中提到了可重入锁ReentrantLock,说有时间再聊,这几天下大雨,《变5》也没有看成,就来和大家一起聊聊Java中的可重入锁。
synchronized与ReentrantLock
从Java官方API中粘过来说明:A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.在这里翻一下(英文不好,强行使用百度翻译加上自己组织):一个可重入的互斥锁,和关键词synchronized隐式锁修饰的方法与语句(可能翻译错了,就理解为和synchronized具有相同作用吧)具有相同的功能和语义,但具有扩展功能,翻译完毕。
通俗来讲可重入锁是一个线程在获取到一个锁以后,再次获取该锁线程不会被阻塞,synchronized是隐式的进行加锁,而ReentrantLock不是隐式的,但是,他们两的功能和语义基本相同,都是可重入锁,下面举个栗子来证明synchronized、ReentrantLock是可重入锁吧。
先来个synchronized可重入的栗子
package edu.thread.reentrantLock;
/**
* @Description: .
* @Author: ZhaoWeiNan .
* @CreatedTime: 2017/6/25 .
* @Version: 1.0 .
*/
public class SynchronizedTest extends Thread {
private String type;
public SynchronizedTest(String type,String name){
super(name);
this.type = type;
}
@Override
public void run() {
if ("死锁".equals(type)){
//死锁栗子
DeadLock();
}else if ("可重入".equals(type)){
//可重入栗子
ReentrantLock();
}
}
/**
* 一个阻塞的栗子。
* 1.老公线程,先用西瓜加锁,拿到锁以后,再去用西瓜刀加锁。
* 2.此时老婆线程已经获取到西瓜刀的锁,然后sleep了。
* 3.老公线程获取不到西瓜刀的锁,所以被阻塞。
*/
private void DeadLock(){
if (Thread.currentThread().getName().equals("老公")){
synchronized ("西瓜"){
System.out.println("老公买了西瓜,准备去拿西瓜刀。");
synchronized("西瓜刀"){
System.out.println("老公拿了西瓜刀,准备吃西瓜。");
}
}
}else if (Thread.currentThread().getName().equals("老婆")){
synchronized ("西瓜刀"){
System.out.println("老婆把西瓜刀藏起来了,不让老公吃西瓜。");
try {
Thread.sleep(1000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 可重入的栗子。
* 1.老公线程获取了徒手吃西瓜的锁,注意此时,徒手吃西瓜的锁并没有被释放。
* 2.同时start了一个老婆线程了,徒手吃西瓜的锁并没有被释放,所以老婆线程没有获取到徒手吃西瓜锁。
* 3.老公线程再次获取徒手吃西瓜的锁,没有被阻塞,证明了synchronized的可重入。
*/
private void ReentrantLock(){
if (Thread.currentThread().getName().equals("老公")){
synchronized ("徒手吃西瓜"){
System.out.println("老公买了西瓜,徒手掰西瓜。");
synchronized("徒手吃西瓜"){
System.out.println("老公掰开了西瓜,准备吃西瓜。");
System.out.println("老公把老婆锁在外面,吃完西瓜才让她进来。");
try {
Thread.sleep(1000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}else if (Thread.currentThread().getName().equals("老婆")){
System.out.println("老婆冲进家。");
synchronized ("徒手吃西瓜"){
System.out.println("老婆抓住老公的手,不让老公吃西瓜。");
}
}
}
}
class Demo{
public static void main(String[] args){
//先运行阻塞的栗子
/*SynchronizedTest lg = new SynchronizedTest("死锁","老公");
SynchronizedTest lp = new SynchronizedTest("死锁","老婆");
lg.start();
lp.start();*/
//在运行可重入的栗子
SynchronizedTest lg = new SynchronizedTest("可重入","老公");
SynchronizedTest lp = new SynchronizedTest("可重入","老婆");
lg.start();
lp.start();
}
}
先运行了一个死锁的栗子,其中老婆线程获取了西瓜刀锁,把老公线程阻塞了,老公线程没有迟到西瓜:
老婆线程获取了西瓜刀锁,阻塞了老公线程
在运行了一个可重入的栗子,其中老公线程获取了徒手吃西瓜锁,此时没有释放,所以阻塞了老婆线程获取徒手吃西瓜锁,当徒手吃西瓜锁释放的情况下,老公再次获取该锁的时候,没有被阻塞:
老公线程获取了徒手吃西瓜锁,再次获取该锁时,没有被阻塞
再来个ReentrantLock可重入的栗子
改写一下上面那个栗子,使用ReentrantLock实现锁:
package edu.thread.reentrantLock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description: .
* @Author: ZhaoWeiNan .
* @CreatedTime: 2017/6/25 .
* @Version: 1.0 .
*/
public class ReentrantLockTest extends Thread {
private String type;
//西瓜锁
private ReentrantLock lock1;
//西瓜刀锁
private ReentrantLock lock2;
//徒手吃西瓜锁
private ReentrantLock lock3;
public ReentrantLockTest(String type,String name,ReentrantLock lock1,ReentrantLock lock2){
super(name);
this.type = type;
this.lock1 = lock1;
this.lock2 = lock2;
}
public ReentrantLockTest(String type,String name,ReentrantLock lock3){
super(name);
this.type = type;
this.lock3 = lock3;
}
@Override
public void run() {
if ("死锁".equals(type)){
//死锁栗子
DeadLock();
}else if ("可重入".equals(type)){
//可重入栗子
ReentrantLock();
}
}
/**
* 一个阻塞的栗子。
* 1.老公线程,先用西瓜加锁,拿到锁以后,再去用西瓜刀加锁。
* 2.此时老婆线程已经获取到西瓜刀的锁,然后sleep了。
* 3.老公线程获取不到西瓜刀的锁,所以被阻塞。
*/
private void DeadLock(){
if (Thread.currentThread().getName().equals("老公")){
lock1.lock();
System.out.println("老公买了西瓜,准备去拿西瓜刀。");
lock2.lock();
System.out.println("老公拿了西瓜刀,准备吃西瓜。");
lock2.unlock();
lock1.unlock();
} else if (Thread.currentThread().getName().equals("老婆")){
lock2.lock();
System.out.println("老婆把西瓜刀藏起来了,不让老公吃西瓜。");
try {
Thread.sleep(1000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock2.unlock();
}
}
/**
* 可重入的栗子。
* 1.老公线程获取了徒手吃西瓜的锁,注意此时,徒手吃西瓜的锁并没有被释放。
* 2.同时start了一个老婆线程了,徒手吃西瓜的锁并没有被释放,所以老婆线程没有获取到徒手吃西瓜锁。
* 3.老公线程再次获取徒手吃西瓜的锁,没有被阻塞,证明了ReentrantLock的可重入。
*/
private void ReentrantLock(){
if (Thread.currentThread().getName().equals("老公")){
lock3.lock();
System.out.println("老公买了西瓜,徒手掰西瓜。");
lock3.lock();
System.out.println("老公掰开了西瓜,准备吃西瓜。");
System.out.println("老公把老婆锁在外面,吃完西瓜才让她进来。");
try {
Thread.sleep(1000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock3.unlock();
}else if (Thread.currentThread().getName().equals("老婆")){
System.out.println("老婆冲进家。");
lock3.lock();
System.out.println("老婆抓住老公的手,不让老公吃西瓜。");
lock3.unlock();
}
}
}
class Demo1 {
public static void main(String[] args){
//先运行阻塞的栗子
/* //西瓜锁
ReentrantLock lock1 = new ReentrantLock();
//西瓜刀锁
ReentrantLock lock2 = new ReentrantLock();
ReentrantLockTest lg = new ReentrantLockTest("死锁","老公",lock1,lock2);
ReentrantLockTest lp = new ReentrantLockTest("死锁","老婆",lock1,lock2);
lg.start();
lp.start();*/
//在运行可重入的栗子
//徒手吃西瓜锁
ReentrantLock lock3 = new ReentrantLock();
ReentrantLockTest lg = new ReentrantLockTest("可重入","老公",lock3);
ReentrantLockTest lp = new ReentrantLockTest("可重入","老婆",lock3);
lg.start();
lp.start();
}
}
先运行了一个死锁的栗子,其中老婆线程获取了西瓜刀锁,把老公线程阻塞了,老公线程没有迟到西瓜:
老婆线程获取了西瓜刀锁,阻塞了老公线程
在运行了一个可重入的栗子,其中老公线程获取了徒手吃西瓜锁,此时没有释放,所以阻塞了老婆线程获取徒手吃西瓜锁,当徒手吃西瓜锁释放的情况下,老公再次获取该锁的时候,没有被阻塞:
老公线程获取了徒手吃西瓜锁,再次获取该锁时,没有被阻塞
利用ReentrantLock实现消费者生产者模式
package edu.thread.reentrantLock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description: .
* @Author: ZhaoWeiNan .
* @CreatedTime: 2017/6/25 .
* @Version: 1.0 .
*/
public class Demo2 {
public static void main(String[] args){
Product product = new Product(new ReentrantLock());
Producer producer = new Producer(product);
Customer customer = new Customer(product);
producer.start();
customer.start();
}
}
/**
* 产品
*/
class Product{
//名称
String name;
//价格
int price;
//可以生产的标识
boolean flag = false;
//锁
ReentrantLock lock;
//消费条件
Condition customerCondition;
//生产条件
Condition producerCondition;
public Product(ReentrantLock lock) {
this.lock = lock;
customerCondition = lock.newCondition();
producerCondition = lock.newCondition();
}
}
/**
* 消费者
*/
class Customer extends Thread{
private Product product;
public Customer(Product product) {
this.product = product;
}
@Override
public void run() {
while (true){
try {
product.lock.lock();
//先判断产品的标识是否可以消费
if (product.flag == true){
//消费
System.out.println("消费了产品");
System.out.println("产品为:" + product.name);
System.out.println("价格为:" + product.price);
//消费了产品,把标注改为false
product.flag = false;
//通知在producerCondition上等待的生产者线程进行生产
product.producerCondition.signal();
}else {
//消费者线程在customerCondition上等待
product.customerCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
product.lock.unlock();
}
}
}
}
/**
* 生产者
*/
class Producer extends Thread{
private Product product;
public Producer(Product product) {
this.product = product;
}
@Override
public void run() {
int i = 0;
while (true){
try {
product.lock.lock();
//产品标识是false没有生产
if (product.flag == false){
if (i % 2 == 0){
//偶数的时候生产cpu
product.name = "CPU";
product.price = 2000;
}else {
//奇数生产内存条
product.name = "内存条";
product.price = 300;
}
i ++;
System.out.println("生产了产品");
System.out.println("产品为:" + product.name);
System.out.println("价格为:" + product.price);
//把产品标识改为true,可以消费
product.flag = true;
//通知在customerCondition等待的消费者线程进行消费
product.customerCondition.signal();
}else {
//已经生产了
//生产者线程在producerCondition上等待
product.producerCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
product.lock.unlock();
}
}
}
}
运行结果
说说ReentrantLock中通知机制的使用方法,在ReentrantLock中通知是用Condition来实现的,Condition对象中的signal方法相当于,Obejct对象中的wait方法,Condition对象中的signal方法相当于Object对象中的notify方法,同理notifyAll、signalAll方法,wait(long timeout)、await(long time, TimeUnit unit)方法作用相同。需要注意的一点是,线程是在Condition对象中等待,也是在Condition对象中被唤醒,拿上面的栗子来说:
//通知在producerCondition上等待的生产者线程进行生产
product.producerCondition.signal();
//消费者线程在customerCondition上等待
product.customerCondition.await();
//生产者线程在producerCondition上等待
product.producerCondition.await();
//通知在customerCondition等待的消费者线程进行消费
product.customerCondition.signal();
如果让生产者线程在producerCondition等待后,如果,如果调用product.customerCondition.signal(),不会唤醒生产者线程,因为生产者线程是在
producerCondition对象中等待的,使用的时候需要注意这一点。
文本中的代码已经上传到开源中国了,有兴趣的小伙伴可以拿去,https://git.oschina.net/zhaoweinan/reentrantlockdemo。
Java中的可重入锁就为大家介绍到这里,欢迎大家来交流,指出文中一些说错的地方,让我加深认识。
谢谢大家!