Java并发编程之Lock
重入锁ReentrantLock
可以代替synchronized, 但synchronized更灵活.
但是, 必须必须必须要手动释放锁.
try {
lock.lock();
} finally {
lock.unlock();
}
重入锁是指任意线程在获取到锁之后能够再次获取该锁而不会被阻塞.
对于ReentrantLock而言, 释放锁时, 锁定调用了n次lock()方法, 那么释放时就需要调用n次unlock()方法.
- tryLock()方法, tryLock(long timeout, TimeUnit unit)方法
尝试锁定,
此方法有返回值, 锁定返回true, 否则返回false.
如果无法锁定或者在一定时间内无法锁定, 线程可以决定是否等待. - lockInterruptibly()方法
线程在请求锁定的时候被阻塞, 如果被interrupt, 则此线程会被唤醒并被要求处理InterruptedException.
再次获取同步状态逻辑:
通过判断当前线程是否为获取锁的线程来决定获取操作是否成功, 如果是获取锁的线程, 则将同步状态增加并返回true, 表示获取同步状态成功.
如果是公平锁, 则还需要判断同步队列中当前节点是否有前驱节点, 如果有, 则需要等待前驱线程获取锁并释放之后才能继续获取锁.
公平锁和非公平锁
公平与否是针对获取锁而言的, 公平的是指获取锁的顺序是按请求时间顺序的, 也就是FIFO.
ReentrantLock默认为非公平锁, 构造对象时调用有参构造方法并参入true, 即为公平锁.
读写锁ReentrantReadWriteLock
读写锁在同一时刻允许多个读线程访问, 但是在写线程访问时, 所有的读线程和写线程都会被阻塞.
读写锁维护了一个读锁, 一个写锁. 并且, 遵循获取写锁再获取读锁, 再释放写锁的次序, 写锁可以阶级为读锁(即锁降级)
ReentrantReadWriteLock也支持重入和公平性选择
ReentrantReadWriteLock实现了ReadWriteLock接口, 此接口中只定义了获取读锁和写锁的方法, 即readLock()和writeLock()两个方法. ReentrantReadWriteLock还提供了如下方法:
| 方法名称 | 描述 |
|---|---|
| int getReadLockCount() | 返回当前读锁被获取的次数. 该次数不等于获取读锁的线程数, 一个线程可多次获取读锁 |
| int getReadHoldCount() | 返回当前线程获取读锁的次数, 并使用ThreadLocal保存 |
| boolean isWriteLocked() | 判断写锁是否被获取 |
| int getWriteHoldCount() | 返回当前写锁被获取的次数 |
ReentrantReadWriteLock通过将AQS的同步状态, 分为高16位(读)和低16位(写)来维护读写锁的获取状态,

图中状态表示, 当前线程已经获取了写锁, 并且重入了两次, 同时获取了两次读锁.
读写锁是通过位运算来确定读写状态的.
设当前同步状态为S, 那么:
写状态为 S & 0x0000FFFF, 即把高位16位(读)抹去
读状态为 S >>> 16, 即右移16位
写状态增加1时, S + 1
读状态增加1时, S + (1 << 16), 即 S + 0x00010000
推论: 同步状态S不等于0时, 当写状态(S & 0x0000FFFF)等于0时, 则读状态(S >>> 16)大于0, 即读锁已被获取.
写锁的获取与释放
写锁支持重入, 但是它是排它锁.
当前线程在获取写锁时:
- 如果当前线程已经获取了写锁, 则写状态增加
- 如果读锁已经被获取或者该线程不是已经获取写锁的线程, 则当前线程进行等待状态
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. if read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c); // 获取写状态
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0), 即上面的推论
if (w == 0 || current != getExclusiveOwnerThread()) // 后面是重入条件
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if ((w == 0 && writerShouldBlock(current)) ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
如果存在读锁, 写锁不能被获取, 因为:
读写锁要确保写锁的操作对读锁可见, 如果允许读锁在已经被获取的情况下对写锁的获取, 那么正在运行的读线程就无法感知到写线程的操作.
写锁的释放, 就是每次释放写状态减小, 写状态为0时表示写锁已释放.
读锁的获取与释放
读锁是支持重入的共享锁.
读锁可以同时被多个线程获取, 在没有写线程访问的情况下, 读锁总是能被成功获取(读状态加增加).
Condition接口
在Java5之前, 等待/通知模式的实现可以采用wait()/notify()/notifyAll()与synchronized配合来实现.
任意Java对象上都有上述方法, 称为监视器方法
Condition接口也提供了类似的方法:
| 方法名称 | 描述 |
|---|---|
| await() | 当前线程进入等待状态, 直到被通知或中断 |
| awaitUninterruptibly() | 当前线程进入等待状态, 直到被通知 |
| awaitNanos(long nanosTimeout) | 当前线程进入等待状态, 直到被通知或中断或超时, 返回值为剩余时间 |
| awaitUtil(Date deadLine) | 当前线程进入等待状态, 直到被通知或中断或到达某个时间, 没到某个时间被通知返回true |
| signal() | 唤醒一个等待在Condition上的线程, 该线程从等待方法返回前必须获得与Condition相关的锁 |
| signalAll() | 唤醒所有等待在Condition上的线程, 可以从等待方法返回的线程必须获得与Condition相关的锁 |
获取Condition对象必须通过Lock的newCondition()方法.
Lock lock = new ReentrantLock();
Condition con1 = lock.newCondition();
Condition con2 = lock.newCondition();
使用时必须先获取锁, 再调用condition的方法, 下面是使用Condition实现一个生产者/消费者场景:
public class MyContainer2<T> {
final private LinkedList<T> lists = new LinkedList<>();
final private int MAX = 10; //最多10个元素
private int count = 0;
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public void put(T t) {
try {
lock.lock();
while(lists.size() == MAX) { //想想为什么用while而不是用if?
producer.await();
}
lists.add(t);
++count;
consumer.signalAll(); //通知消费者线程进行消费
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public T get() {
T t = null;
try {
lock.lock();
while(lists.size() == 0) {
consumer.await();
}
t = lists.removeFirst();
count --;
producer.signalAll(); //通知生产者进行生产
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return t;
}
public static void main(String[] args) {
MyContainer2<String> c = new MyContainer2<>();
//启动消费者线程
for(int i=0; i<10; i++) {
new Thread(()->{
for(int j=0; j<5; j++) System.out.println(c.get());
}, "c" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动生产者线程
for(int i=0; i<2; i++) {
new Thread(()->{
for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
}, "p" + i).start();
}
}
}
参考资料: 《Java并发编程的艺术》
Java并发编程之Lock的更多相关文章
- Java并发编程之Lock(同步锁、死锁)
这篇文章是接着我上一篇文章来的. 上一篇文章 同步锁 为什么需要同步锁? 首先,我们来看看这张图. 这是一个程序,多个对象进行抢票. package MovieDemo; public class T ...
- Java并发编程之CAS
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...
- Java并发编程之CAS第一篇-什么是CAS
Java并发编程之CAS第一篇-什么是CAS 通过前面几篇的学习,我们对并发编程两个高频知识点了解了其中的一个—volatitl.从这一篇文章开始,我们将要学习另一个知识点—CAS.本篇是<凯哥 ...
- Java并发编程之CAS二源码追根溯源
Java并发编程之CAS二源码追根溯源 在上一篇文章中,我们知道了什么是CAS以及CAS的执行流程,在本篇文章中,我们将跟着源码一步一步的查看CAS最底层实现原理. 本篇是<凯哥(凯哥Java: ...
- Java并发编程之CAS第三篇-CAS的缺点及解决办法
Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...
- Java并发编程之set集合的线程安全类你知道吗
Java并发编程之-set集合的线程安全类 Java中set集合怎么保证线程安全,这种方式你知道吗? 在Java中set集合是 本篇是<凯哥(凯哥Java:kagejava)并发编程学习> ...
- Java并发编程之synchronized关键字
整理一下synchronized关键字相关的知识点. 在多线程并发编程中synchronized扮演着相当重要的角色,synchronized关键字是用来控制线程同步的,可以保证在同一个时刻,只有一个 ...
- Java 并发编程之 Condition 接口
本文部分摘自<Java 并发编程的艺术> 概述 任意一个 Java 对象,都拥有一个监视器方法,主要包括 wait().wait(long timeout).notify() 以及 not ...
- Java并发编程之AQS
一.什么是AQS AQS(AbstractQueuedSynchronize:队列同步器)是用来构建锁或者其他同步组件的基础框架,很多同步类都是在它的基础上实现的,比如常用的ReentrantLock ...
随机推荐
- 获取dmp文件的schema
白天的时候,做了一个获取dmp文件的schema实验,特此记录一下. 参考文章:如何获取dmp文件的schema -- by 我的烟灰缸 http://oradb.cc/2017/07/10/%E5 ...
- java中的notify和notifyAll有什么区别?
先说两个概念:锁池和等待池 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入 ...
- PHP-CGI,FASTcgi,php-fpm,之间的关系?
刚开始对这个问题我也挺纠结的,看了<HTTP权威指南>后,感觉清晰了不少.首先,CGI是干嘛的?CGI是为了保证web server传递过来的数据是标准格式的,方便CGI程序的编写者. ...
- Hook 无侵入式埋点(页面统计)
一.技术原理 Method-Swizzling 黑魔法 方法交换(不懂的可以查) 二.页面统计 某盟页面统计SDK需要开发者在APP基类里实现ViewDidAppear和viewDidDisappea ...
- [UWP]创建一个ProgressControl
1. 前言 博客园终于新增了UWP的分类,我来为这个分类贡献第一篇博客吧. UWP有很多问题,先不说生态的事情,表单.验证.输入.设计等等一堆基本问题缠身.但我觉得最应该首先解决的绝对是Blend,那 ...
- Java 并发基础——线程安全性
当线程安全:多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类都能表现出正确的行为,那么久称这个类是线程安全的. 在线程 ...
- Ubuntu安装MariaDB教程
一.环境 服务器:Ubuntu 16.04.1 LTS(GUN/Linux 4.4.0-91-generic x86_64) 数据库版本:MariaDB 10.3 二.安装流程 2.1 进入Maria ...
- Bate敏捷冲刺每日报告--day3
1 团队介绍 团队组成: PM:齐爽爽(258) 小组成员:马帅(248),何健(267),蔡凯峰(285) Git链接:https://github.com/WHUSE2017/C-team 2 ...
- 将数组写入Plist文件中
-(void)writeToPlist:(NSArray *)uploadingfiles Name:(NSString *)name { NSMutableArr ...
- bzoj千题计划242:bzoj4034: [HAOI2015]树上操作
http://www.lydsy.com/JudgeOnline/problem.php?id=4034 dfs序,树链剖分 #include<cstdio> #include<io ...