Java多线程(七):ReentrantLock
加锁和解锁
我们来看下ReentrantLock的基本用法
ThreadDomain35类
public class ThreadDomain35 {
private Lock lock = new ReentrantLock();
public void testMethod()
{
try
{
lock.lock();
for (int i = 0; i < 2; i++)
{
System.out.println("ThreadName = " + Thread.currentThread().getName() + ", i = " + i);
}
}
finally
{
lock.unlock();
}
}
}
线程和main方法
public class MyThread35 extends Thread {
private ThreadDomain35 td;
public MyThread35(ThreadDomain35 td)
{
this.td = td;
}
public void run()
{
td.testMethod();
}
public static void main(String[] args)
{
ThreadDomain35 td = new ThreadDomain35();
MyThread35 mt0 = new MyThread35(td);
MyThread35 mt1 = new MyThread35(td);
MyThread35 mt2 = new MyThread35(td);
mt0.start();
mt1.start();
mt2.start();
}
}
输出结果
ThreadName = Thread-2, i = 0
ThreadName = Thread-2, i = 1
ThreadName = Thread-0, i = 0
ThreadName = Thread-0, i = 1
ThreadName = Thread-1, i = 0
ThreadName = Thread-1, i = 1
一个线程必须执行完才能执行下一个线程,说明ReentrantLock可以加锁。
ReentrantLock持有的对象监视器和synchronized不同
ThreadDomain37类,methodB用synchronized修饰
public class ThreadDomain37 {
private Lock lock = new ReentrantLock();
public void methodA()
{
try
{
lock.lock();
System.out.println("MethodA begin ThreadName = " + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("MethodA end ThreadName = " + Thread.currentThread().getName());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public synchronized void methodB()
{
System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
}
}
MyThread37_0类
public class MyThread37_0 extends Thread {
private ThreadDomain37 td;
public MyThread37_0(ThreadDomain37 td)
{
this.td = td;
}
public void run()
{
td.methodA();
}
}
MyThread37_1类
public class MyThread37_1 extends Thread {
private ThreadDomain37 td;
public MyThread37_1(ThreadDomain37 td)
{
this.td = td;
}
public void run()
{
td.methodB();
}
}
MyThread37_main方法
public class MyThread37_main {
public static void main(String[] args)
{
ThreadDomain37 td = new ThreadDomain37();
MyThread37_0 mt0 = new MyThread37_0(td);
MyThread37_1 mt1 = new MyThread37_1(td);
mt0.start();
mt1.start();
}
}
运行结果如下
MethodA begin ThreadName = Thread-0
MethodB begin ThreadName = Thread-1
MethodB begin ThreadName = Thread-1
MethodA end ThreadName = Thread-0
加了synchronized依然是异步执行,说明ReentrantLock和synchronized持有的对象监视器不同。ReentrantLock需要手动加锁和释放锁。
Condition
基本用法
synchronized与wait()和nitofy()/notifyAll()方法可以实现等待/唤醒模型,ReentrantLock同样可以,需要借助Condition的await()和signal/signalAll(),await()释放锁。
ThreadDomain38类
public class ThreadDomain38 {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void await()
{
try
{
lock.lock();
System.out.println("await时间为:" + System.currentTimeMillis());
condition.await();
System.out.println("await等待结束");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public void signal()
{
try
{
lock.lock();
System.out.println("signal时间为:" + System.currentTimeMillis());
condition.signal();
System.out.println("signal等待结束");
}
finally
{
lock.unlock();
}
}
}
MyThread38类,线程和main方法
public class MyThread38 extends Thread
{
private ThreadDomain38 td;
public MyThread38(ThreadDomain38 td)
{
this.td = td;
}
public void run()
{
td.await();
}
public static void main(String[] args) throws Exception
{
ThreadDomain38 td = new ThreadDomain38();
MyThread38 mt = new MyThread38(td);
mt.start();
Thread.sleep(3000);
td.signal();
}
}
运行结果如下
await时间为:1563505465346
signal时间为:1563505468345
signal等待结束
await等待结束
可以看到,ReentrantLock和Condition实现了等待/通知模型。
一个Lock可以创建多个Condition;
notify()唤醒的线程是随机的,signal()可以有选择性地唤醒。
Condition选择 唤醒/等待
现在看一个利用Condition选择等待和唤醒的例子
ThreadDomain47类,定义add和sub方法
public class ThreadDomain47 {
private final Lock lock = new ReentrantLock();
private final Condition addCondition = lock.newCondition();
private final Condition subCondition = lock.newCondition();
private static int num = 0;
private List<String> lists = new LinkedList<String>();
public void add() {
lock.lock();
try {
while(lists.size() == 10) {//当集合已满,则"添加"线程等待
addCondition.await();
}
num++;
lists.add("add Banana" + num);
System.out.println("The Lists Size is " + lists.size());
System.out.println("The Current Thread is " + "增加线程");
System.out.println("==============================");
this.subCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {//释放锁
lock.unlock();
}
}
public void sub() {
lock.lock();
try {
while(lists.size() == 0) {//当集合为空时,"减少"线程等待
subCondition.await();
}
String str = lists.get(0);
lists.remove(0);
System.out.println("The Token Banana is [" + str + "]");
System.out.println("The Current Thread is " + "减少线程");
System.out.println("==============================");
num--;
addCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
MyThread40_0类,增加线程
public class MyThread40_0 implements Runnable {
private ThreadDomain47 task;
public MyThread40_0(ThreadDomain47 task) {
this.task = task;
}
@Override
public void run() {
task.add();
}
}
MyThread40_1类,减少线程
public class MyThread40_1 implements Runnable {
private ThreadDomain47 task;
public MyThread40_1(ThreadDomain47 task) {
this.task = task;
}
@Override
public void run() {
task.sub();
}
}
main方法,启动线程
public class MyThread40_main {
public static void main(String[] args) {
ThreadDomain47 task = new ThreadDomain47();
Thread t1=new Thread(new MyThread40_0(task));
Thread t3=new Thread(new MyThread40_0(task));
Thread t7=new Thread(new MyThread40_0(task));
Thread t8=new Thread(new MyThread40_0(task));
Thread t2 = new Thread(new MyThread40_1(task));
Thread t4 = new Thread(new MyThread40_1(task));
Thread t5 = new Thread(new MyThread40_1(task));
Thread t6 = new Thread(new MyThread40_1(task));
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
t7.start();
t8.start();
}
}
输出结果如下
The Lists Size is 1
The Current Thread is 增加线程
==============================
The Lists Size is 2
The Current Thread is 增加线程
==============================
The Token Banana is [add Banana1]
The Current Thread is 减少线程
==============================
The Token Banana is [add Banana2]
The Current Thread is 减少线程
==============================
The Lists Size is 1
The Current Thread is 增加线程
==============================
The Token Banana is [add Banana1]
The Current Thread is 减少线程
==============================
The Lists Size is 1
The Current Thread is 增加线程
==============================
The Token Banana is [add Banana1]
The Current Thread is 减少线程
==============================
可以看到,lists的数量不会增加太多,也不会减少太多。当集合满,使增加线程等待,唤醒减少线程;当集合空,使减少线程等待,唤醒增加线程。我们用wait()/notify()机制无法实现该效果,这里体现了Condition的强大之处。
ReentrantLock中的方法
公平锁和非公平锁
ReentrantLock可以指定公平锁和非公平锁,公平锁根据线程运行的顺序获取锁,非公平锁则通过抢占获得锁,不按线程运行顺序。synchronized是非公平锁。在ReentrantLock(boolean fair)构造函数传入true/false来指定公平锁/非公平锁。
看个例子
ThreadDomain39类和main方法
public class ThreadDomain39 {
private Lock lock = new ReentrantLock(true);
public void testMethod()
{
try
{
lock.lock();
System.out.println("ThreadName" + Thread.currentThread().getName() + "获得锁");
}
finally
{
lock.unlock();
}
}
public static void main(String[] args) throws Exception
{
final ThreadDomain39 td = new ThreadDomain39();
Runnable runnable = new Runnable()
{
public void run()
{
System.out.println("线程" + Thread.currentThread().getName() + "运行了");
td.testMethod();
}
};
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++)
threads[i] = new Thread(runnable);
for (int i = 0; i < 5; i++)
threads[i].start();
}
}
输出结果如下
线程Thread-0运行了
ThreadNameThread-0获得锁
线程Thread-1运行了
线程Thread-2运行了
ThreadNameThread-1获得锁
线程Thread-3运行了
线程Thread-4运行了
ThreadNameThread-2获得锁
ThreadNameThread-3获得锁
ThreadNameThread-4获得锁
可以看到公平锁获得锁的顺序和线程运行的顺序相同。公平锁尽可能地让线程获取锁的顺序和线程运行顺序保持一致,再执行几次,可能不一致。
ReentrantLock构造函数传入false,输出结果如下:
线程Thread-0运行了
线程Thread-2运行了
线程Thread-4运行了
线程Thread-3运行了
ThreadNameThread-0获得锁
线程Thread-1运行了
ThreadNameThread-1获得锁
ThreadNameThread-2获得锁
ThreadNameThread-4获得锁
ThreadNameThread-3获得锁
非公平锁获得锁的顺序和线程运行的顺序不同
getHoldCount()
获取当前线程调用lock()的次数,一般debug使用。
看个例子
public class ThreadDomain40 {
private ReentrantLock lock = new ReentrantLock();
public void testMethod1()
{
try
{
lock.lock();
System.out.println("testMethod1 getHoldCount = " + lock.getHoldCount());
testMethod2();
}
finally
{
lock.unlock();
}
}
public void testMethod2()
{
try
{
lock.lock();
System.out.println("testMethod2 getHoldCount = " + lock.getHoldCount());
}
finally
{
lock.unlock();
}
}
public static void main(String[] args)
{
ThreadDomain40 td = new ThreadDomain40();
td.testMethod1();
}
}
输出结果如下
testMethod1 getHoldCount = 1
testMethod2 getHoldCount = 2
可以看到,testMethod1()被调用了一次,testMethod2()被调用了两次,ReentrantLock和synchronized一样,锁都是可重入的。
getQueueLength()和isFair()
getQueueLength()获取等待的线程数量,isFair()判断是否是公平锁。
ThreadDomain41类和main方法,Thread.sleep(2000)使第一个线程之后的线程都来不及启动,Thread.sleep(Integer.MAX_VALUE)使线程无法unlock()。
public class ThreadDomain41 {
public ReentrantLock lock = new ReentrantLock();
public void testMethod()
{
try
{
lock.lock();
System.out.println("ThreadName = " + Thread.currentThread().getName() + "进入方法!");
System.out.println("是否公平锁?" + lock.isFair());
Thread.sleep(Integer.MAX_VALUE);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException
{
final ThreadDomain41 td = new ThreadDomain41();
Runnable runnable = new Runnable()
{
public void run()
{
td.testMethod();
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++)
threads[i] = new Thread(runnable);
for (int i = 0; i < 10; i++)
threads[i].start();
Thread.sleep(2000);
System.out.println("有" + td.lock.getQueueLength() + "个线程正在等待!");
}
}
输出结果如下
ThreadName = Thread-1进入方法!
是否公平锁?false
有9个线程正在等待!
ReentrantLock默认是非公平锁,只有一个线程lock(),9个线程在等待。
hasQueuedThread()和hasQueuedThreads()
hasQueuedThread(Thread thread)查询指定线程是否在等待锁,hasQueuedThreads()查询是否有线程在等待锁。
看个例子
ThreadDomain41类和main方法,和上面例子类似,Thread.sleep(Integer.MAX_VALUE); 让线程不释放锁,Thread.sleep(2000);让第一个线程之后的线程都无法启动。
public class ThreadDomain42 extends ReentrantLock {
public void waitMethod()
{
try
{
lock();
Thread.sleep(Integer.MAX_VALUE);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
unlock();
}
}
public static void main(String[] args) throws InterruptedException
{
final ThreadDomain42 td = new ThreadDomain42();
Runnable runnable = new Runnable()
{
public void run()
{
td.waitMethod();
}
};
Thread t0 = new Thread(runnable);
t0.start();
Thread.sleep(500);
Thread t1 = new Thread(runnable);
t1.start();
Thread.sleep(500);
Thread t2 = new Thread(runnable);
t2.start();
Thread.sleep(500);
System.out.println("t0 is waiting?" + td.hasQueuedThread(t0));
System.out.println("t1 is waiting?" + td.hasQueuedThread(t1));
System.out.println("t2 is waiting?" + td.hasQueuedThread(t2));
System.out.println("Is any thread waiting?" + td.hasQueuedThreads());
}
}
输出结果如下
t0 is waiting?false
t1 is waiting?true
t2 is waiting?true
Is any thread waiting?true
t0线程获得了锁,t0没有释放锁,导致t1,t2等待锁。
isHeldByCurrentThread()和isLocked()
isHeldByCurrentThread()判断锁是否由当前线程持有,isLocked()判断锁是否由任意线程持有。
请看示例
ThreadDomain43类和main方法
public class ThreadDomain43 extends ReentrantLock {
public void testMethod()
{
try
{
lock();
System.out.println(Thread.currentThread().getName() + "线程持有了锁!");
System.out.println(Thread.currentThread().getName() + "线程是否持有锁?" +
isHeldByCurrentThread());
System.out.println("是否任意线程持有了锁?" + isLocked());
} finally
{
unlock();
}
}
public void testHoldLock()
{
System.out.println(Thread.currentThread().getName() + "线程是否持有锁?" +
isHeldByCurrentThread());
System.out.println("是否任意线程持有了锁?" + isLocked());
}
public static void main(String[] args)
{
final ThreadDomain43 td = new ThreadDomain43();
Runnable runnable0 = new Runnable()
{
public void run()
{
td.testMethod();
}
};
Runnable runnable1 = new Runnable()
{
public void run()
{
td.testHoldLock();
}
};
Thread t0 = new Thread(runnable0);
Thread t1 = new Thread(runnable1);
t0.start();
t1.start();
}
}
输出结果如下
Thread-0线程持有了锁!
Thread-1线程是否持有锁?false
Thread-0线程是否持有锁?true
是否任意线程持有了锁?true
是否任意线程持有了锁?true
Thread-0线程testMethod方法持有锁,Thread-1线程testHoldLock方法没有lock操作,所以不持有锁。
tryLock()和tryLock(long timeout, TimeUnit unit)
tryLock()有加锁的功能,获得了锁且锁没有被另外一个线程持有,此时返回true,否则返回false,可以有效避免死锁。tryLock(long timeout, TimeUnit unit)表示在给定的时间内获得了锁,锁没有被其他线程持有,且不处于中断状态。返回true,否则返回false;
看个例子
public class MyThread39 {
public static void main(String[] args) {
System.out.println("开始");
final Lock lock = new ReentrantLock();
new Thread() {
@Override
public void run() {
String tName = Thread.currentThread().getName();
if (lock.tryLock()) {
System.out.println(tName + "获取到锁!");
} else {
System.out.println(tName + "获取不到锁!");
return;
}
try {
for (int i = 0; i < 5; i++) {
System.out.println(tName + ":" + i);
}
Thread.sleep(5000);
} catch (Exception e) {
System.out.println(tName + "出错了!");
} finally {
System.out.println(tName + "释放锁!");
lock.unlock();
}
}
}.start();
new Thread() {
@Override
public void run() {
String tName = Thread.currentThread().getName();
try {
if (lock.tryLock(1,TimeUnit.SECONDS)) {
System.out.println(tName + "获取到锁!");
} else {
System.out.println(tName + "获取不到锁!");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
for (int i = 0; i < 5; i++) {
System.out.println(tName + ":" + i);
}
} catch (Exception e) {
System.out.println(tName + "出错");
} finally {
System.out.println(tName + "释放锁!");
lock.unlock();
}
}
}.start();
System.out.println("结束");
}
}
输出结果如下
开始
Thread-0获取到锁!
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
结束
Thread-1获取不到锁!
Thread-0释放锁!
Thread-0先获得了锁,且sleep了5秒,导致Thread-1获取不到锁,我们给Thread-1的tryLock设置1秒,一秒内获取不到锁就会返回false。
如果Thread.sleep(0),那么Thread-0和Thread-1都可以获得锁,园友可以自己试下。
synchronized和ReentrantLock的比较
1.synchronized关键字是语法层面的实现,ReentrantLock要手动lock()和unlock();
2.synchronized是不公平锁,ReentrantLock可以指定是公平锁还是非公平锁;
3.synchronized等待/唤醒机制是随机的,ReentrantLock借助Condition的等待/唤醒机制可以自行选择等待/唤醒;
Java多线程(七):ReentrantLock的更多相关文章
- java多线程(七)-线程之间的 协作
对于多线程之间的共享受限资源,我们是通过锁(互斥)的方式来进行保护的,从而避免发生受限资源被多个线程同时访问的问题.那么线程之间既然有互斥,那么也会有协作.线程之间的协作也是必不可少的,比如 盖个商场 ...
- Java多线程——<七>多线程的异常捕捉
一.概述 为什么要单独讲多线程的异常捕捉呢?先看个例子: public class ThreadException implements Runnable{ @Override public void ...
- Java多线程——<八>多线程其他概念
一.概述 到第八节,就把多线程基本的概念都说完了.把前面的所有文章加连接在此: Java多线程——<一>概述.定义任务 Java多线程——<二>将任务交给线程,线程声明及启动 ...
- java多线程系列(七)---Callable、Future和FutureTask
Callable.Future和FutureTask 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量 ...
- Java多线程(九)之ReentrantLock与Condition
一.ReentrantLock 类 1.1 什么是reentrantlock java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 ...
- Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock
本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...
- java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...
- java多线程系列(四)---ReentrantLock的使用
Lock的使用 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理 ...
- “全栈2019”Java多线程第七章:等待线程死亡join()方法详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java 多线程基础(七)线程休眠 sleep
Java 多线程基础(七)线程休眠 sleep 一.线程休眠 sleep sleep() 方法定义在Thread.java中,是 static 修饰的静态方法.sleep() 的作用是让当前线程休眠, ...
随机推荐
- 【码云周刊第 23 期】Web 高效开发必备的 PHP 框架(从这里学起)good
码云项目推荐 1.项目名称:多功能 THinkPHP 开源框架 项目简介:使用 THinkPHP 开发项目的过程中把一些常用的功能或者第三方 sdk 整合好,开源供亲们参考,如 Auth 权限管理.支 ...
- 瑞芯微RK3399宣布系统开源,进入百余种行业市场!
集微网消息,2月24日瑞芯微官方突然宣布, Rockchip RK3399Linux系统开源!作为Rockchip旗舰级芯片,RK3399具有高性能.高扩展.全能型应用特性. 这一重磅消息立马刷爆朋友 ...
- 一个类的实例化对象所占空间的大小(对象大小= vptr(可能不止一个) + 所有非静态数据成员大小 + Aligin字节大小(依赖于不同的编译器))
注意不要说类的大小,是类的对象的大小. 首先,类的大小是什么?确切的说,类只是一个类型定义,它是没有大小可言的. 用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小. 如果 Class ...
- 深入理解JVM(一)虚拟机内存
一 .前言 JVM是什么,我想诸位肯定都清楚. 好吧,我还是简答说一下JVM即Java虚拟机(够简单吧 233333). 虽然说,所有抛开操作系统,讲虚拟机的内容,都是耍流氓.但是,贫僧不修善果,就爱 ...
- 使用fastjson读取超巨json文件引起的GC问题
项目中需要将巨量数据生成的json文件解析,并写入数据库,使用了 alibaba 的 fastjson,在实践过程中遇到了 GC 问题,记录如下: 数据大约为70万条,文件大小在3~4G左右,使用 f ...
- vue实现Excel文件的上传与下载
一.前言项目中使用到比较多的关于Excel的前端上传与下载,整理出来,以便后续使用或分析他人. 1.前端vue:模板下载与导入Excel 导入Excel封装了子组件,点击导入按钮可调用子组件,打开文件 ...
- JVM(七):JVM内存结构
JVM(七):JVM内存结构 在前几节的文章我们多次讲到 Class 对象需要分配入 JVM 内存,并在 JVM 内存中执行 Java 代码,完成对象内存的分配.执行.回收等操作,因此,如今让我们来走 ...
- Cisco packet tracer下dhcp的配置的vlan的应用
话不多说,先上拓扑图. pc0和pc1分别接在三层交换机的F0/1.F0/2接口,ser接在F0/24接口,用ser用作dhcp的服务器. 0x01:配置server0 先配置server的IP地址. ...
- [apue] 管道原子写入量的一个疑问
PIPE_BUF定义了管道可原子写入的数据量,在我的系统(CentOS 6.7)上这个值是4096,写了个程序验证了一下,通过三个维度来考察: N: 生产者数量 M:每个生产者的生产次数 P:每次写入 ...
- 高并发架构系列:Redis缓存和MySQL数据一致性方案详解
一.需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库. 这个业务场景, ...