加锁和解锁

我们来看下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的更多相关文章

  1. java多线程(七)-线程之间的 协作

    对于多线程之间的共享受限资源,我们是通过锁(互斥)的方式来进行保护的,从而避免发生受限资源被多个线程同时访问的问题.那么线程之间既然有互斥,那么也会有协作.线程之间的协作也是必不可少的,比如 盖个商场 ...

  2. Java多线程——<七>多线程的异常捕捉

    一.概述 为什么要单独讲多线程的异常捕捉呢?先看个例子: public class ThreadException implements Runnable{ @Override public void ...

  3. Java多线程——<八>多线程其他概念

    一.概述 到第八节,就把多线程基本的概念都说完了.把前面的所有文章加连接在此: Java多线程——<一>概述.定义任务 Java多线程——<二>将任务交给线程,线程声明及启动 ...

  4. java多线程系列(七)---Callable、Future和FutureTask

    Callable.Future和FutureTask 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量 ...

  5. Java多线程(九)之ReentrantLock与Condition

    一.ReentrantLock 类   1.1 什么是reentrantlock   java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 ...

  6. Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

    本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...

  7. java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...

  8. java多线程系列(四)---ReentrantLock的使用

    Lock的使用 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理 ...

  9. “全栈2019”Java多线程第七章:等待线程死亡join()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  10. Java 多线程基础(七)线程休眠 sleep

    Java 多线程基础(七)线程休眠 sleep 一.线程休眠 sleep sleep() 方法定义在Thread.java中,是 static 修饰的静态方法.sleep() 的作用是让当前线程休眠, ...

随机推荐

  1. Unity开发概览(HoloLens开发系列)

    本文翻译自:Unity development overview 要开始使用Unity创建全息应用,点此安装包含Unity HoloLens技术预览的开发工具.Unity HoloLens技术预览基于 ...

  2. QQ邮箱打败163邮箱的过程(重视用户体验的结果)

    引用 楼主 CKAOS 的回复: 目前负责一个项目,ASP.NET的,做一个网盘系统,别的都弄好了,只差一个下载文件夹的功能未实现,只能在服务器端打包成压缩文件,再发回浏览器.如何直接下载文件夹,不是 ...

  3. Linux下卸载QT SDK

    unbuntu下卸载QT方法一:you can remove it like this, those developers should add this somewhere ! like next ...

  4. 还在羡慕BAT等公司的大流量的架构吗,commonrpc 是一个以netty 传输协议框架为基础(支持FTP)

    还在羡慕BAT等公司的大流量的架构吗?让你的java系统引用解耦,互相独立,commonrpc 就可以办到.commonrpc 是一个以netty 传输协议框架为基础, 自定义 spring shce ...

  5. Qt使用MinGW编译,如何忽略警告

    Qt编译时经常出现以下警告: warning: unused parameter 'arg1' [-Wunused-parameter] warning: unused variable 'i' [- ...

  6. jconsole远程监控logstash agent

    在logstash的jvm.options文件末尾添加: -Dcom.sun.management.jmxremote.port=9999   //指定jmx端口-Dcom.sun.managemen ...

  7. spark streaming 接收kafka消息之四 -- 运行在 worker 上的 receiver

    使用分布式receiver来获取数据使用 WAL 来实现 exactly-once 操作: conf.set("spark.streaming.receiver.writeAheadLog. ...

  8. 03 我的第一个html页面

    <!--定义文档的类型,一个html就是一个文档--> <!DOCTYPE html> <html lang="en"> <!--head ...

  9. aspose授权亲测可用配套代码

    支持excel,word,ppt,pdf using Aspose.Cells; using Aspose.Words.Saving; using ESBasic; using OMCS.Engine ...

  10. 微信小程序地图开发总结

    最近在做一个微信小程序地图插件,通过传入起始位置名称和经纬度信息,就可以跳转到路线规划插件页面中,在该页面中,可以根据起始位置查询自驾,公共交通,步行等方式的路线信息,并且在地图上显示路线信息,在这个 ...