Java多线程核心技术(四)Lock的使用
本文主要介绍使用Java5中Lock对象也能实现同步的效果,而且在使用上更加方便。
本文着重掌握如下2个知识点:
- ReentrantLock 类的使用。
- ReentrantReadWriteLock 类的使用。
1. 使用ReentrantLock 类
在Java多线程中,可以使用 synchronized 关键字来实现线程之间同步互斥,但在JDK1.5中新增加了 ReentrantLock 类也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上也比 synchronized 更加的灵活。
1.1 使用ReentrantLock实现同步
调用ReentrantLock对象的lock()方法获取锁,调用unlock()方法释放锁。
下面是初步的程序示例:
public class Demo {
private Lock lock = new ReentrantLock();
public void test(){
lock.lock();
for (int i= 0;i<5;i++){
System.out.println(Thread.currentThread().getName()+" - "+i);
}
lock.unlock();
}
public static void main(String[] args) {
Demo demo = new Demo();
for (int i = 0;i<5;i++){
new Thread(new Runnable() {
@Override
public void run() {
demo.test();
}
}).start();
}
}
}
运行结果:
Thread-0 - 0
Thread-0 - 1
Thread-0 - 2
Thread-0 - 3
Thread-0 - 4
Thread-1 - 0
Thread-1 - 1
Thread-1 - 2
Thread-1 - 3
Thread-1 - 4
Thread-2 - 0
Thread-2 - 1
Thread-2 - 2
Thread-2 - 3
Thread-2 - 4
Thread-3 - 0
Thread-3 - 1
Thread-3 - 2
Thread-3 - 3
Thread-3 - 4
Thread-4 - 0
Thread-4 - 1
Thread-4 - 2
Thread-4 - 3
Thread-4 - 4
从运行的结果来看,当前线程打印完毕后将锁进行释放,其他线程才可以继续打印。
1.1.2 锁住类的所有实例对象
上面的示例是所有线程调用一个ReentrantLock实例对象实现同步,如果每个线程都调用各自ReentrantLock实例对象的同一段代码呢?
示例代码:
public class MyService implements Runnable{
private ReentrantLock lock = new ReentrantLock();
public void method(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"锁定...");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"解锁。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
new Thread(new MyService()).start();
new Thread(new MyService()).start();
new Thread(new MyService()).start();
}
@Override
public void run() {
method();
}
}
运行结果:
Thread-0锁定...
Thread-2锁定...
Thread-1锁定...
Thread-2解锁。
Thread-0解锁。
Thread-1解锁。
从运行结果来看,并没有实现想要的方法同步的效果。如果我们想要实现类似synchronized(class),也就是给Class类上锁,可以把 ReentrantLock 声明为 static 静态变量。
示例代码:
public class MyService implements Runnable{
private static ReentrantLock lock = new ReentrantLock();
public void method(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"锁定...");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"解锁。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
new Thread(new MyService()).start();
new Thread(new MyService()).start();
new Thread(new MyService()).start();
}
@Override
public void run() {
method();
}
}
运行结果:
Thread-0锁定...
Thread-0解锁。
Thread-1锁定...
Thread-1解锁。
Thread-2锁定...
Thread-2解锁。
从运行结果来看,成功实现了预期的结果。
1.2 使用Condition 实现等待 / 通知
关键字 synchronized 与 wait() 和 notify() / notifyAll() 方法相结合可以实现等待 / 通知模式,类 ReentrantLock 也可以实现同样的功能,但需要借助于 Condition(即对象监视器)实例,线程对象可以注册在指定的 Condition 中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。
在使用 notify() / notifyAll() 方法进行通知时,被通知的线程却是由JVM随机选择的。但使用 ReentrantLock 结合 Condition 类是可以实现前面介绍过的“选择性通知”,这个功能是非常重要的,而且在 Condition 类中是默认提供的。
示例代码:
public class Demo {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void await() {
try {
lock.lock();
System.out.println("开始等待:" + System.currentTimeMillis());
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signal() {
try {
lock.lock();
System.out.println("结束等待:" + System.currentTimeMillis());
condition.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.await();
}
}).start();
Thread.sleep(3000);
demo.signal();
}
}
运行结果:
开始等待:1537352883839
结束等待:1537352886839
成功实现等待 / 通知模式。
在Object中,有wait() 、wait(long)、notify()、notifyAll()方法。
在Condition类中,有 await()、await(long)、signal()、signalAll()方法。
1.3使用多个Condition实现通知部分线程
示例代码:
public class Demo {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
public void awaitA() {
try {
lock.lock();
System.out.println("A开始等待:" + System.currentTimeMillis());
conditionA.await();
System.out.println("A结束等待:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB() {
try {
lock.lock();
System.out.println("B开始等待:" + System.currentTimeMillis());
conditionB.await();
System.out.println("B结束等待:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signalAll_B() {
try {
lock.lock();
conditionB.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.awaitA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo.awaitB();
}
}).start();
Thread.sleep(3000);
demo.signalAll_B();
}
}
运行结果:
A开始等待:1537354021740
B开始等待:1537354021741
B结束等待:1537354024738
可以看到,只有B线程被唤醒了。
通过此实验可知,使用 ReentrantLock 对象可以唤醒指定种类的线程,这是控制部分线程行为的方便行为。
1.4 公平锁和非公平锁
锁Lock分为”公平锁“和“非公平锁”,公平锁表示线程获取锁的顺序是按照线程加载的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平的了。
设置公平锁:
Lock lock = new ReentrantLock(true);
使用ReentrantLock类设置公平锁只需要在构造时传入boolean参数即可。默认false。需要明白的是,即使设置为true也不能保证百分百公平。
总结:
公平锁:先去判断等待队列是否为空,也就是是否有线程在等待,没有就去获取锁,否则把自己加入等待队列。
非公平锁:先去尝试获取锁,如果失败再加入到等待队列。
1.5 方法getHoldCount()、getQueryLength()和getWaitQueryLength()
1.方法getHoldCount() 的作用是查询当前线程保持此锁定的个数,也就是调用 lock() 方法的次数。
示例代码:
public class Service {
private ReentrantLock lock = new ReentrantLock();
public void method() {
try {
lock.lock();
System.out.println("getHoldCount() " + lock.getHoldCount());
method2();
} finally {
lock.unlock();
}
}
public void method2() {
try {
lock.lock();
System.out.println("getHoldCount() " + lock.getHoldCount());
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Service service = new Service();
service.method();
}
}
运行结果:
getHoldCount() 1
getHoldCount() 2
2.方法getQueryLength() 的作用是返回正等待获取此锁定的线程估计数。比如有5个方法,1个线程首先执行 await()方法,那么在调用getQueueLength()方法后返回值是4,说明有4个线程同时在等待 lock 的释放。
示例代码:
public class Service {
private ReentrantLock lock = new ReentrantLock();
public void method() {
try {
lock.lock();
System.out.println("Name: " + Thread.currentThread().getName());
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
Runnable runnable = new Runnable() {
@Override
public void run() {
service.method();
}
};
for (int i = 0; i < 5; i++) {
new Thread(runnable).start();
}
Thread.sleep(1000);
ReentrantLock lock = service.getLock();
System.out.println("有多少线程在等待:"+lock.getQueueLength());
}
private ReentrantLock getLock() {
return lock;
}
}
运行结果:
Name: Thread-1
有多少线程在等待:4
3.方法getWaitQueryLength(condition) 的作用是返回等待与此锁定相关的给定条件Condition的线程估计数,比如有5个线程,每个线程都执行了同一个condition 对象的await() 方法,则调用 getWaitQueryLength(condition) 方法时返回的int值是5。
示例代码:
public class Service {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void method() {
try {
lock.lock();
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void notifyMethod() {
try {
lock.lock();
System.out.println("等待condition的线程数" + lock.getWaitQueueLength(condition));
condition.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
Runnable runnable = new Runnable() {
@Override
public void run() {
service.method();
}
};
for (int i = 0; i < 5; i++) {
new Thread(runnable).start();
}
Thread.sleep(1000);
service.notifyMethod();
}
}
运行结果:
等待condition的线程数5
1.6 方法hasQueuedThread()、hasQueuedThreads()和hasWaiters()
1.方法 boolean hasQueuedThread(Thread thread) 的作用是查询指定的线程是否正在等待获取此锁定。
2.方法 boolean hasQueuedThreads() 的作用是查询是否有线程正在等待获取此锁定。
1、2示例代码:
public class Service {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void waitMethod(){
try {
lock.lock();
Thread.sleep(Integer.MAX_VALUE);
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public ReentrantLock getLock(){
return lock;
}
public static void main(String[] args) throws InterruptedException {
final Service service = new Service();
Runnable runnable = new Runnable() {
@Override
public void run() {
service.waitMethod();
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
Thread.sleep(1000);
ReentrantLock lock = service.getLock();
System.out.println(lock.hasQueuedThreads());
System.out.println(lock.hasQueuedThread(thread1));
System.out.println(lock.hasQueuedThread(thread2));
}
}
运行结果:
true
false
true
3.方法 boolean hasWaiters(Condition condition) 的作用是查询是否有线程正在等待与此锁定有关的 condition 条件。
示例代码:
public class Service {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void waitMethod(){
try {
lock.lock();
condition.await();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void notifyMethod(){
try {
lock.lock();
System.out.println("有没有线程正在等待 condition ?" + lock.hasWaiters(condition) + " 线程数是多少?" + lock.getWaitQueueLength(condition));
condition.signalAll();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
Runnable runnable = new Runnable() {
@Override
public void run() {
service.waitMethod();
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
Thread.sleep(2000);
service.notifyMethod();
}
}
运行结果:
有没有线程正在等待 condition ?true 线程数是多少?10
1.7 方法isFair()、isHeldByCurrentThread()和isLocked()
- 方法boolean isFair() 的作用是判断是不是公平锁。
- 方法boolean isHeldByCurrentThread() 的作用是查询当前线程是否保持此锁定。
- 方法boolean isLocked() 的作用是查询此锁定是否由任意线程保持。
更改上面的部分代码:
System.out.println(lock.isHeldByCurrentThread());
System.out.println(lock.isLocked());
lock.lock();
System.out.println(lock.isLocked());
System.out.println(lock.isHeldByCurrentThread());
运行结果:
false
false
true
true
1.8 方法lockInterruptibly()、tryLock()和tryLock(long timeout, TimeUnit unit)
下面的三个方法都是对lock.lock()方法的另一种变形:
方法void lockInterruptibly()的作用是:如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。
而使用 lock() 方法,即使线程被中断(调用thread.interrupt()方法),也不会出现异常。
方法boolean tryLock() 的作用是,仅在未被另一个线程保持的情况下,才获取该锁定。
假设有两个线程同时调用同一个lock对象的tryLock()方法,那么除了第一个获得锁(返回true),其它都获取不到锁(返回false)。
方法 boolean tryLock(long timeout, TimeUnit unit) 的作用是,如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。
1.9 方法 condition.awaitUninterruptibly()的使用
前面讲到,执行condition.await()方法后,线程进入等待状态,如果这时线程被中断(调用thread.interrupt()方法)则会抛出异常。而使用 condition.awaitUninterruptibly() 方法代替 condition.await() 方法则不会抛出异常。
1.10 方法 condition.awaitUntil(Date deadline)的使用
使用方法 condition.awaitUntil(Date deadline) 可以代替 await(long time, TimeUnit unit) 方法进行线程等待,该方法在等待时间到达前是可以被提前唤醒的。
1.11 使用Condition实现顺序执行
使用Condition对象可以对线程执行的业务进行排序规划。
示例代码:
public class DThread{
volatile private static int nextPrintWho = 1;
private static ReentrantLock lock = new ReentrantLock();
final private static Condition conditionA = lock.newCondition();
final private static Condition conditionB = lock.newCondition();
final private static Condition conditionC = lock.newCondition();
public static void main(String[] args) {
Thread threadA = new Thread(){
@Override
public void run() {
try {
lock.lock();
while (nextPrintWho != 1){
conditionA.await();
}
for (int i = 0;i<3;i++){
System.out.println("ThreadA "+(i+1));
}
nextPrintWho = 2;
conditionB.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
};
Thread threadB = new Thread(){
@Override
public void run() {
try {
lock.lock();
while (nextPrintWho != 2){
conditionA.await();
}
for (int i = 0;i<3;i++){
System.out.println("ThreadB "+(i+1));
}
nextPrintWho = 3;
conditionB.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
};
Thread threadC = new Thread(){
@Override
public void run() {
try {
lock.lock();
while (nextPrintWho != 3){
conditionA.await();
}
for (int i = 0;i<3;i++){
System.out.println("ThreadC "+(i+1));
}
nextPrintWho = 1;
conditionB.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
};
for (int i= 0;i<5;i++){
new Thread(threadA).start();
new Thread(threadB).start();
new Thread(threadC).start();
}
}
}
打印结果:
ThreadA 1
ThreadA 2
ThreadA 3
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC 1
ThreadC 2
ThreadC 3
....
2.使用ReentrantReadWriteLock类
类 ReentrantLock 具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock() 方法后面的任务。这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的。所以在JDK中提供了一种读写锁 ReentrantReadWriteLock 类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写 ReentrantReadWriteLock 来提升该方法的代码运行速度。
读写锁表示也有两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。在没有线程 Thread进行写入操作时,进行读取操作的多个 Thread 都可以获取读锁,而进行写入操作的 Thread 只有在获取写锁后才能进行写入操作。即多个 Thread可以同时进行读取操作但是同一时刻只允许一个 Thread 进行写入操作。
总结起来就是:读读共享,写写互斥,读写互斥,写读互斥。
声明读写锁:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
获取读锁:
lock.readLock().lock();
获取写锁:
lock.writeLock().lock();
3.文末总结
学习完本文完全可以使用Lock对象将 synchronized关键字替换掉,而且其具有的独特功能也是 synchronized 所不具有的。在学习并发时,Lock是synchronized关键字的进阶,掌握Lock有助于学习并发包中源代码的实现原理,在并发包中大量的类使用了Lock 接口作为同步的处理方式。
参考
《Java多线程编程核心技术》高洪岩著
扩展
Java多线程核心技术(四)Lock的使用的更多相关文章
- java多线程系列(四)---Lock的使用
Lock的使用 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理 ...
- Java多线程核心技术(六)线程组与线程异常
本文应注重掌握如下知识点: 线程组的使用 如何切换线程状态 SimpleDataFormat 类与多线程的解决办法 如何处理线程的异常 1.线程的状态 线程对象在不同运行时期有不同的状态,状态信息就处 ...
- Java多线程核心技术(五)单例模式与多线程
本文只需要考虑一件事:如何使单例模式遇到多线程是安全的.正确的 1.立即加载 / "饿汉模式" 什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 ...
- 从ConcurrentHashMap的演进看Java多线程核心技术 Java进阶(六)
本文分析了HashMap的实现原理,以及resize可能引起死循环和Fast-fail等线程不安全行为.同时结合源码从数据结构,寻址方式,同步方式,计算size等角度分析了JDK 1.7和JDK 1. ...
- java多线程系列(四)---ReentrantLock的使用
Lock的使用 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理 ...
- Java多线程(四)java中的Sleep方法
点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...
- “全栈2019”Java多线程第四章:设置和获取线程名称
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- java 多线程 synchronized与lock的通信机制等问题,结合相应实例说明
1. 利用多线程实现如下需求: 写两个线程,一个线程打印1~52,另一个线程打印A~Z,打印顺序是12A34B...5152Z: 2. 使用synchronized 实现 public class T ...
- 160711、Java 多线程核心技术梳理
本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,lock 的使用,定时器,单例模式,以及线程状态与线程组. java 多线程 基础知识 创建线程的两种方式:1 ...
随机推荐
- Android--底部导航栏的动态替换方案
1.通常来说,一般情况下,我们的app的BottomTab会有集中实现方式. 自定义view,然后自己写逻辑去实现互斥. 自由度最高,因为啥都是自己写的. 使用RadioGroup+RadioButt ...
- matlab练习程序(地图上画经纬度)
需要看下生成的数据在地球上的经纬度具体位置. 投影为墨卡托投影. clear all; close all; clc; load coast; a=load('out.txt'); %自己的经纬度 ...
- NumPy的使用(一)
# -*- coding: utf8 -*- from numpy import* a=arange(15).reshape(3,5) print a print a.shape print a.nd ...
- HDFS客户端的权限错误:Permission denied
报错:Permission denied: user=root, access=WRITE, inode="hadoop":hadoop:supergroup:rwxr-xr-x ...
- Git Extensions 使用小结
1.查看仓库 2.创建分支 然后会自动创建一个 Commit ,推送到远端分支即可. 3.合并分支 注意1.自动提交需要没有无法自动合并的冲突才行. 注意2.快进线指的是将别人的提交原封不动附加到自己 ...
- Linux下完全删除用户
实验环境:Centos7虚拟机 首先创建一个普通用户gubeiqing. [root@localhost ~]# useradd gubeiqing [root@localhost ~]# passw ...
- 【Beta】博客合集
[Beta Scrum]冲刺! 1/5 [Beta Scrum]冲刺! 2/5 [Beta Scrum]冲刺! 3/5 [Beta Scrum]冲刺! 4/5 [Beta Scrum]冲刺! 5/5
- python3编写网络爬虫18-代理池的维护
一.代理池的维护 上面我们利用代理可以解决目标网站封IP的问题 在网上有大量公开的免费代理 或者我们也可以购买付费的代理IP但是无论是免费的还是付费的,都不能保证都是可用的 因为可能此IP被其他人使用 ...
- IOC的底层实现
- 从此使用linux系统,但是QQ是必不可少的!!该篇文章方法成功!!!已验证!!!!!
一开始,我在Ubuntu14.04下安装的QQ版本是WineQQ2013SP6-20140102-Longene, 但后来发现这个版本QQ在linux下问题很多,比如不能用键盘输入密码,QQ表情使用失 ...