加锁和解锁

我们来看下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. Delphi访问活动目录(使用COM,活动目录Active Directory是用于Windows Server的目录服务)

    活动目录Active Directory是用于Windows Server的目录服务,它存储着网络上各种对象的有关信息,并使该信息易于管理员和用户查找及使用.Active Directory使用结构化 ...

  2. OpenSSL所有版本的变化,从1.1开始架构有所变化,生成的lib名称也有所不同了,以及对Qt的影响

    The complete explanation is that 1.0.x and 1.1.x do not have the same naming conventions for the gen ...

  3. Qt设置窗体的透明度: setWindowOpacity

    在Qt中,设置窗体透明度的函数有:void   setWindowOpacity(qreal level)   特性: 透明度的有效范围从1.0(完全不透明)到0.0(完全透明的). 默认情况下,此属 ...

  4. QML动画概述(几十篇相关博客)

    QML提供了丰富的动画元素,说起动画,无非是给UI增光添彩罢了.在QML中,动画常常与State和Transition联系在一起,这几个概念(下面的例子中都用到了)都比较简单,相关介绍可查看Qt官方文 ...

  5. [java代码库]-简易计算器(第二种)

    [java代码库]-简易计算器(第二种) 第二种方案:在程序中不使用if/switch……case等语句,完成计算器功能. <html> <head> <title> ...

  6. 做了一个浏览指定文件格式的 TreeView(方便查看Source目录下的源码)

    unit DirTreeView; interface uses   SysUtils, Classes, Controls, Forms, ComCtrls; type   TDirTreeView ...

  7. Windows Phone8.1系统新特性

    Windows Phone 8.1 beta SDK已经为大家透露了不少WP8.1系统的新特性,不过这些新特性还不能保证在最终的消费者版本中都有所体现,毕竟它还仅是SDK版本.日前,国外媒体WPCen ...

  8. myeclipse2018的下载安装教程

    首先注意事项!!!!!!! 在安装破解前是不可以打开软件的 jdk版本不能是10版本,1.8或1.9都可以 附上MyEclipse2018的百度云下载链接: 链接:https://pan.baidu. ...

  9. Spark之SparkSql

    -- Spark SQL 以编程方式指定模式 val sqlContext = new org.apache.spark.sql.SQLContext(sc) val employee = sc.te ...

  10. sentinel 滑动窗口统计机制

    sentinel的滑动窗口统计机制就是根据当前时间,获取对应的时间窗口,并更新该时间窗口中的各项统计指标(pass/block/rt等),这些指标被用来进行后续判断,比如限流.降级等:随着时间的推移, ...