【JDK1.8】JUC.Lock综述
一、前言
前段时间结束了jdk1.8集合框架的源码阅读,在过年的这段时间里,一直在准备JUC(java.util.concurrent)的源码阅读。平时接触的并发场景开发并不很多,但是有网络的地方,就存在并发,所以想找几本书阅读深入一下,看到网上推荐较多的两本书《Java并发编程实战》和《Java多线程编程核心技术》。看了两书的优缺点后,笔者选择了先看后者,据说代码例子较多,书到手后,看完后的印象就是对并发的关键字、几个常见类的api进行了介绍,内容挺早以前,讲的也是不是很深,对Java SE5新加的类介绍很少,只能说对于刚接触并发编程的人来说,还是值得一看的。
二、java.util.concurrent.locks图概览
在JUC的包里面,有一个包专门用于存放锁相关的类,笔者将其中的大部分内容整理进了下面UML中:
具体关系大家可以去看看UML的关系图,顺便介绍个生成UML的工具:PlantUml,UML界的markdown,真的挺好用。
图中要提的是:圆圈里面有个+的关系,代表内部类,笔者为了图片看的更简洁,把ReentrantLock
、Semaphore
等类中的内部类Sync
合到了一起,其实它们是一个类,只不过都叫这个名字。
从图中我们可以看到,关系较为紧密的是AbstractQueuedSynchronizer抽象类,而它则直接依赖了LockSupport这个类,笔者将在后面先分析这个类的源码。
三、基础接口的源码解析
3.1 Lock接口
在JDK1.5以后,添加了Lock接口,它用于实现与Synchronized关键字相同的锁操作,来实现多个线程控制对共享资源的访问。但是能提供更加灵活的结构,可能具有完全不同的属性,并且可能支持多个相关的Condition对象。基本用法如下:
Lock l = ...;
l.lock();
try {
// 访问被锁保护的资源
} finally {
l.unlock();
}
下面我们来简单看一下它下面的具体内容:
public interface Lock {
// 获得锁资源
void lock();
// 尝试获得锁,如果当前线程被调用了interrupted则中断,并抛出异常,否则就获得锁
void lockInterruptibly() throws InterruptedException;
// 判断能否获得锁,如果能获得,则获得锁,并返回true(此时已经获得了锁)
boolean tryLock();
// 保持给定的等待时间,如果期间能拿到锁,则获得锁,同样如果期间被中断,则抛异常
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 返回与此Lock对象绑定Condition实例
Condition newCondition();
}
其中,tryLock只会尝试一次,如果返回false,则走false的流程,不会一直让线程一直等待。
3.2 Condition接口
Condition与Lock要结合使用,使用Condition可以用来实现wait()
和notify()/notifyAll()
类似的等待/通知模式。与Object对象里不同的是,Condition更加灵活,可以在一个Lock对象里创建多个Condition实例,有选择的进行线程通知,在线程调度上更加灵活。使用Condition注释上的例子:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
// 当count等于数组的大小时,当前线程等待,直到notFull通知,再进行生产
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
// 当count为0,进入等待,直到notEmpty通知,进行消费。
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
可以通过多个线程来调用put和take方法,来模拟生产者和消费者。
我们来换成常规的wait/notify的实现方式:
class BoundedBuffer {
private final Object lock;
public BoundedBuffer(Object lock) {
this.lock = lock;
}
public void put(Object x) {
try {
synchronized (items) {
while (count == items.length) {
items.wait();
}
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
// items.notify();
items.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public Object take() {
try {
synchronized (items) {
while (count == 0) {
items.wait();
}
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
// items.notify();
items.notifyAll();
return x;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
如果将items.notifyAll()
换成items.notify()
,在多生产者和多消费者模式情况下,可能出现take唤醒了take的情况,导致生产者在等待消费者消费,而消费者等待生产者生产,最终导致程序无限等待,而用notifyAll(),则唤醒所有的生产者和消费者,不像Condition可以选择性的通知。下面我们来看一下它的源码:
public interface Condition {
// 让当前线程等待,直到被通知或者被中断
void await() throws InterruptedException;
// 与前者的区别是,当等待过程中被中断时,仍会继续等待,直到被唤醒,才会设置中断状态
void awaitUninterruptibly();
// 让当前线程等待,直到它被告知或中断,或指定的等待时间已经过。
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 与上面的类似,让当前线程等待,不过时间单位是纳秒
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 让当前线程等待到确切的指定时间,而不是时长
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒一个等待当前condition的线程,有多个则随机选一个
void signal();
// 唤醒所有等待当前condition的线程
void signalAll();
}
3.3 ReadWriteLock接口
读写锁与一般的互斥锁不同,它分为读锁和写锁,在同一时间里,可以有多个线程获取读锁来进行共享资源的访问。如果此时有线程获取了写锁,那么读锁的线程将等待,直到写锁释放掉,才能进行共享资源访问。简单来说就是读锁与写锁互斥。
读写锁比互斥锁允许对于共享数据更大程度的并发。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。ReadWriteLock适用于读多写少的并发情况。
public interface ReadWriteLock {
// 返回写锁
Lock writeLock();
// 返回读锁
Lock readLock();
}
再看一下源码里提供的示例:
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
// 获得写锁
rwl.readLock().lock();
// 缓存无效,则重写数据
if (!cacheValid) {
// 在获得写锁之前,必须先释放读锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// 重写检查一次,因为其他线程可能在这段时间里获得了写锁,并且修改了状态
if (!cacheValid) {
data = ...
cacheValid = true;
}
// 在释放写锁之前,通过获取读锁来降级。
rwl.readLock().lock();
} finally {
// 释放写锁
rwl.writeLock().unlock();
}
}
// cacheValid,直接获取数据,并释放读锁
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
ReentrantReadWriteLock中,读锁可以获取写锁,而返过来,写锁不能获得读锁,所以在上面代码中,要先释放写锁,再获取读锁,具体的源码分析后面再细说。
四、总结
开了个新坑,边看边学。最后谢谢各位园友观看,如果有描述不对的地方欢迎指正,与大家共同进步!
【JDK1.8】JUC.Lock综述的更多相关文章
- JUC.Lock(锁机制)学习笔记[附详细源码解析]
锁机制学习笔记 目录: CAS的意义 锁的一些基本原理 ReentrantLock的相关代码结构 两个重要的状态 I.AQS的state(int类型,32位) II.Node的waitStatus 获 ...
- jdk1.5多线程Lock接口及Condition接口
jdk1.5多线程的实现的方式: jdk1.5之前对锁的操作是隐式的 synchronized(对象) //获取锁 { } //释放锁 jdk1.5锁的操作是显示的:在包java.util.concu ...
- 多线程-线程间通信-多生产者多消费者问题(JDK1.5后Lock,Condition解决办法及开发中代码范例)
1 package multithread4; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurre ...
- JDK1.5中LOCK,Condition的使用
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.uti ...
- Jdk1.6 JUC源码解析(13)-LinkedBlockingQueue
功能简介: LinkedBlockingQueue是一种基于单向链表实现的有界的(可选的,不指定默认int最大值)阻塞队列.队列中的元素遵循先入先出 (FIFO)的规则.新元素插入到队列的尾部,从队列 ...
- Jdk1.6 JUC源码解析(12)-ArrayBlockingQueue
功能简介: ArrayBlockingQueue是一种基于数组实现的有界的阻塞队列.队列中的元素遵循先入先出(FIFO)的规则.新元素插入到队列的尾部,从队列头部取出元素. 和普通队列有所不同,该队列 ...
- Jdk1.6 JUC源码解析(6)-locks-AbstractQueuedSynchronizer
功能简介: AbstractQueuedSynchronizer(以下简称AQS)是Java并发包提供的一个同步基础机制,是并发包中实现Lock和其他同步机制(如:Semaphore.CountDow ...
- Jdk1.6 JUC源码解析(7)-locks-ReentrantLock
功能简介: Java代码层面提供的锁机制,可做为Synchronized(jvm内置)的替代物,和Synchronized一样都是可重入的. 与Synchronized相比较而言,ReentrantL ...
- Jdk1.6 JUC源码解析(1)-atomic-AtomicXXX
转自:http://brokendreams.iteye.com/blog/2250109 功能简介: 原子量和普通变量相比,主要体现在读写的线程安全上.对原子量的是原子的(比如多线程下的共享变量i+ ...
随机推荐
- [Python Study Notes]cpu信息
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ...
- 用Python发送邮件
文件:send.py # -*- coding:utf-8 -*- # ## 任兴测试用Python发送邮件 import os import sys import getopt import tim ...
- is there any way to stop auto block
shadowsocks出现错误日志 tail /var/log/ssserver.log 2017-07-02 12:36:31 ERROR: block all requests from 10.4 ...
- centos安装软件依赖问题
yum install gcc gcc-c++ ncurses-devel perl 基础包安装
- Java经典编程题50道之六
输入两个正整数m和n,求其最大公约数和最小公倍数. public class Example06 { public static void main(String[] args) { ...
- 读《Linux Shell脚本攻略》(第2版) 总结
前段时间读完了<Linux Shell脚本攻略>(第2版)这本书,给部分想读这本书的人分享下个人感受. 说下这本书的难度吧.纯新手或者只懂少部分编程知识的人,读起来还是有很大难度的.以我为 ...
- rxjs-流式编程
前言 第一次接触rxjs也是因为angular2应用,内置了rxjs的依赖,了解之后发现它的强大,是一个可以代替promise的框架,但是只处理promise的东西有点拿尚方宝剑砍蚊子的意思. 如果我 ...
- string (KMP+期望DP)
Time Limit: 1000 ms Memory Limit: 256 MB Description 给定一个由且仅由字符 'H' , 'T' 构成的字符串$S$. 给定一个最初为空的字符串 ...
- Docker系统五:Docker仓库
创建Docker Hub账户 登录和上传镜像到Hub.docker.com docker login //登陆hub.docker.com docker tag ubutun1404-baseimag ...
- spring 配置文件无法加载,junit找不到xml配置文件java.lang.IllegalStateException: Failed to load ApplicationContext
最近遇到一个奇怪的问题.maven项目再进行junit单元测试的时候发现无法加载配置文件.一会能加载一会又不能加载.然后试了在src/main/resource下面的配置文件放到src/test/re ...