在前面ConcurrentHashMap的實現(xiàn)原理與使用(二)中提到了可重入鎖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.此時老婆線程已經(jīng)獲取到西瓜刀的鎖,然后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實現(xiàn)鎖:
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.此時老婆線程已經(jīng)獲取到西瓜刀的鎖,然后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實現(xiàn)消費者生產(chǎn)者模式
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();
}
}
/**
* 產(chǎn)品
*/
class Product{
//名稱
String name;
//價格
int price;
//可以生產(chǎn)的標識
boolean flag = false;
//鎖
ReentrantLock lock;
//消費條件
Condition customerCondition;
//生產(chǎn)條件
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();
//先判斷產(chǎn)品的標識是否可以消費
if (product.flag == true){
//消費
System.out.println("消費了產(chǎn)品");
System.out.println("產(chǎn)品為:" + product.name);
System.out.println("價格為:" + product.price);
//消費了產(chǎn)品,把標注改為false
product.flag = false;
//通知在producerCondition上等待的生產(chǎn)者線程進行生產(chǎn)
product.producerCondition.signal();
}else {
//消費者線程在customerCondition上等待
product.customerCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
product.lock.unlock();
}
}
}
}
/**
* 生產(chǎn)者
*/
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();
//產(chǎn)品標識是false沒有生產(chǎn)
if (product.flag == false){
if (i % 2 == 0){
//偶數(shù)的時候生產(chǎn)cpu
product.name = "CPU";
product.price = 2000;
}else {
//奇數(shù)生產(chǎn)內存條
product.name = "內存條";
product.price = 300;
}
i ++;
System.out.println("生產(chǎn)了產(chǎn)品");
System.out.println("產(chǎn)品為:" + product.name);
System.out.println("價格為:" + product.price);
//把產(chǎn)品標識改為true,可以消費
product.flag = true;
//通知在customerCondition等待的消費者線程進行消費
product.customerCondition.signal();
}else {
//已經(jīng)生產(chǎn)了
//生產(chǎn)者線程在producerCondition上等待
product.producerCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
product.lock.unlock();
}
}
}
}

說說ReentrantLock中通知機制的使用方法,在ReentrantLock中通知是用Condition來實現(xiàn)的,Condition對象中的signal方法相當于,Obejct對象中的wait方法,Condition對象中的signal方法相當于Object對象中的notify方法,同理notifyAll、signalAll方法,wait(long timeout)、await(long time, TimeUnit unit)方法作用相同。需要注意的一點是,線程是在Condition對象中等待,也是在Condition對象中被喚醒,拿上面的栗子來說:
//通知在producerCondition上等待的生產(chǎn)者線程進行生產(chǎn)
product.producerCondition.signal();
//消費者線程在customerCondition上等待
product.customerCondition.await();
//生產(chǎn)者線程在producerCondition上等待
product.producerCondition.await();
//通知在customerCondition等待的消費者線程進行消費
product.customerCondition.signal();
如果讓生產(chǎn)者線程在producerCondition等待后,如果,如果調用product.customerCondition.signal(),不會喚醒生產(chǎn)者線程,因為生產(chǎn)者線程是在
producerCondition對象中等待的,使用的時候需要注意這一點。
文本中的代碼已經(jīng)上傳到開源中國了,有興趣的小伙伴可以拿去,https://git.oschina.net/zhaoweinan/reentrantlockdemo。
Java中的可重入鎖就為大家介紹到這里,歡迎大家來交流,指出文中一些說錯的地方,讓我加深認識。
謝謝大家!