【概述】

重入锁可以完全代替synchronized关键字。

与synchronized相比,重入锁ReentrantLock有着显示的操作过程,即开发人员必须手动指定何时加锁,何时释放锁,所以重入锁对于逻辑控制的灵活性好于synchronized。

要注意的是,每次在退出临界区时,必须记得释放锁,否则其他线程将没有机会访问临界区了。

【ReentrantLock入门例子】

package com.higgin.reentrantLock;

import java.util.concurrent.locks.ReentrantLock;

/**
* Created by HigginCui on 2017/5/15.
*/
public class ReentrantLockThread implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); //实例化一个重入锁 public static int num = 0; @Override
public void run() {
for(int i =0; i<100; i++){
lock.lock(); //上锁
try{
num++;
}finally {
lock.unlock(); //释放锁
}
}
} public static void main(String[] args) throws InterruptedException{
ReentrantLockThread rlThread = new ReentrantLockThread();
Thread t1 = new Thread(rlThread);
Thread t2 = new Thread(rlThread); t1.start();
t2.start(); t1.join();
t2.join(); System.out.println(num);
}
}

【运行结果】

【为什么叫重入锁】

对于一个线程,这种锁是可以重复进入的。一个线程可以两次获得同一个锁。

有一点要注意,如果一个线程多次获得锁,那么释放锁必须有相同的次数。

如果释放锁的次数多了,会抛出一个java.lang.IllegalMonitorStateException异常。

如果释放锁的次数少了,相当于还持有这个锁,其他线程无法进入临界区。

lock.lock();
lock.lock(); //一个线程可以多次获得同一个锁
try{
num++;
}finally {
lock.unlock();
lock.unlock(); //锁必须释放相同的次数
}

【ReentrantLock的几个重要方法】

1.lock():获得锁,如果锁被占用,则等待。

2.lockInterruptibly():获得锁,但会优先响应中断。

3.tryLock():尝试获得锁,返回true/false,该方法不等待,立即返回。

4.tryLock(long time, Timeunit unit):在给定的时间内尝试获得锁。

5.unlock():释放锁。

【中断响应】

对于synchronized来说,如果一个线程在等待wait,那么结果只有两种情况:1.获得这把锁继续执行;2.继续保持等待。

对于重入锁,提供了第3种可能:3.线程还可以被中断。

 

【利用重入锁的中断响应来中断结束线程,解决死锁问题】

package com.higgin.reentrantLock;

import java.util.concurrent.locks.ReentrantLock;

/**
* Created by HigginCui on 2017/5/16.
*/
public class DeadLockThread implements Runnable{ public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); boolean flag; public DeadLockThread(boolean flag){
this.flag = flag;
} @Override
public void run() {
try {
if (flag == false) {
lock1.lockInterruptibly(); //获得lock1锁,优先响应中断,即在等待的过程中,可以响应中断
Thread.sleep();
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly(); //获得lcok2锁,优先响应中断,顺序与上面的相反
Thread.sleep();
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println(Thread.currentThread().getName()+"被中断啦!");
} finally {
if (lock1.isHeldByCurrentThread()) { //判断当前线程是否持有lock1锁
lock1.unlock(); //如果持有lock1锁,就释放
}
if(lock2.isHeldByCurrentThread()){ //再判断当前线程是否持有lock2锁,如果持有就释放
lock2.unlock();
}
System.out.println(Thread.currentThread().getName()+"线程退出啦!!");
}
} public static void main(String[] args) throws InterruptedException{
DeadLockThread d1 = new DeadLockThread(true);
DeadLockThread d2 = new DeadLockThread(false); Thread t1 = new Thread(d1,"t1");
Thread t2 = new Thread(d2,"t2"); t1.start();
t2.start(); //同时启动这两个线程,必定会产生死锁 Thread.sleep(); //main线程延时5秒
t2.interrupt(); //为t2线程产生一个中断,如果没有这个中断,两个线程都会处于死锁状态,都在等待对方释放锁
}
}

【运行结果】

【分析】

线程t1启动了,然后启动t2,

t1先占用lock1,等待500ms,

t2先占用lock2,等待500ms,

t1在500ms等待结束后,想获得lock2,但此时lock2已经被t2占用,

反之t2在500ms等待结束后,想获得lock1,但此时lcok1又被t1占用,

于是陷入了死锁状态。

由于lock.lcokInterruptibly()是一个可以对中断进行相应的锁申请动作,在线程等待的过程中,可以响应中断。

main线程在5000ms后中断了t2线程,那么t2会进入catch捕获异常,打印相关的异常信息,然后进入finally代码块释放当前线程t2持有的锁lock2,t2退出。

此时t1就可以获得t2释放的lock2,然后也进入finally代码块,释放其持有的两个锁lock1和lock2。

【申请锁设置等待时间tryLock(10,TimeUnit.SECONDS)】

这也是避免死锁的一种方法,即申请锁限时等待,给定一个等待时间,如果线程在这段时间内无法申请获得锁,那么线程会自动放弃。

【tryLock(long time, Timeunit unit)的例子】

package com.higgin.reentrantLock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; /**
* Created by HigginCui on 2017/5/17.
*/
public class TimeLockThread implements Runnable { ReentrantLock lock = new ReentrantLock(); @Override
public void run() {
try{
if (lock.tryLock(, TimeUnit.SECONDS)){ //如果当前线程5秒内无法获得对应的锁,那么其会自动释放锁
System.out.println(Thread.currentThread().getName()+"获得锁,并占用6秒!");
Thread.sleep(); //一旦获得锁,那么回占用锁的时间为6秒
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
if(lock.isHeldByCurrentThread()){
System.out.println(Thread.currentThread().getName()+"释放锁啦,然后退出!");
lock.unlock();
}else{
System.out.println(Thread.currentThread().getName()+"没有占用锁,直接退出!");
}
}
} public static void main(String[] args) {
TimeLockThread tt = new TimeLockThread();
Thread t1 = new Thread(tt,"t1");
Thread t2 = new Thread(tt,"t2"); t1.start();
t2.start();
}
}

【运行结果】

【分析】

本例中,占用锁的线程会持有锁6秒的时间,故另一个线程无法在5秒内获得锁,因此请求锁会失败。

如果tryLock( )不带参数,当前线程会尝试获得锁,如果锁并未被其他线程占用,则申请锁会成功,并立即返回true。

如果锁被其他线程占用,则当前线程不会进行等待,而是立即返回false。

tryLock()这种方式不会引起线程等待,因此也不会产生死锁。

【公平锁】

public ReentrantLock( boolean fair ); //fair=true,公平锁

公平锁会按照时间的顺序,保证先到者先获得锁,后到者后获得锁。公平锁最大的一个特点就是:不会产生饥饿现象。只要你排队,最终还是可以等到锁的。(如果使用synchronized关键字进行锁控制,那么产生的锁就是非公平的)

公平锁看起来很优美,但是其性能也非常低下,因此默认情况下,锁是非公平的,若没有特殊的需求,也不要使用公平锁。

17_重入锁ReentrantLock的更多相关文章

  1. synchronized关键字,Lock接口以及可重入锁ReentrantLock

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  2. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  3. 轻松学习java可重入锁(ReentrantLock)的实现原理

    转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...

  4. java 可重入锁ReentrantLock的介绍

    一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...

  5. 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  6. Java 显示锁 之 重入锁 ReentrantLock(七)

    ReentrantLock 重入锁简介 重入锁 ReentrantLock,顾名思义,就是支持同一个线程对资源的重复加锁.另外,该锁还支持获取锁时的公平与非公平性的选择. 重入锁 ReentrantL ...

  7. Java中可重入锁ReentrantLock原理剖析

    本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一. 概述 本文首先介绍Lock接口.ReentrantLock的类层次结构以及锁功能模板类AbstractQue ...

  8. Java多线程——深入重入锁ReentrantLock

    简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”. ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现 ...

  9. java线程的同步控制--重入锁ReentrantLock

    我们常用的synchronized关键字是一种最简单的线程同步控制方法,它决定了一个线程是否可以访问临界区资源.同时Object.wait() 和Object.notify()方法起到了线程等待和通知 ...

随机推荐

  1. kibana安装汉化包

    kibana安装汉化包其实很简单!但要找到汉化包可能就很麻烦了.我这里提供了6.2的版本的汉化包!至于能不能在其他版本用,我就没试过了.但6.2的kibana本人亲测.没问题!!!! 下载——解压.这 ...

  2. springboot配置文件的所有属性

    转载:https://blog.csdn.net/qq_28929589/article/details/79439795 # spring boot application.properties配置 ...

  3. Android TCP协议的Socket通信

    1.介绍 2.使用方法 3.java后台代码 服务器server package com.lucky.servertest; import java.io.BufferedReader; import ...

  4. C++_函数3-引用变量与函数的默认参数

    引用变量 C++新增了一种复合类型——引用变量. 引用是已定义的变量的别名.例如将twain作为clement变量的引用,则可以交替使用twain和clement来表示该变量. 引用变量的主要用途:用 ...

  5. matlab中的linkage和cluster函数

    Linkage: Agglomerative hierarchical cluster tree(凝聚成层次聚类树) 语法: 解释: Z=linkage(x),返回Z,是一个X矩阵中行的分层聚类树(用 ...

  6. 洛谷 P3205 [HNOI2010]合唱队

    题目链接 题解 区间dp \(f[i][j]\)表示i~j区间最后一次插入的是\(a[i]\) \(g[i][j]\)表示i~j区间最后一次插入的是\(a[j]\) 然后就是普通区间dp转移 Code ...

  7. QDU_CEF(补)

    C - Arthur and Table Arthur has bought a beautiful big table into his new flat. When he came home, A ...

  8. 那些熟悉又陌生的 css2、css3 样式,持续复习

    initial关键字:    除了 Internet Explorer,其他的主流浏览器都支持 initial 关键字. Opera 15 之前的版本不支持 initial 关键字. initial ...

  9. 搭建Flask+Vue及配置Vue 基础路由

    最近一直在看关于Python的东西,准备多学习点东西.以前的项目是用Vue+Java写的,所以试着在升级下系统的前提下.能不能使用Python+Vue做一遍. 选择Flask的原因是不想随大流,并且比 ...

  10. JS如何获取屏幕、浏览器及网页高度宽度?

    屏幕的尺寸是指当前分辨率下的高度.宽度,而不是物理高度.宽度. 如:一个22寸的显示器,屏幕分辨率为1366 * 768,那么我们可以获取到的屏幕高度为1366px,宽度为768px. 屏幕宽度和高度 ...