并发编程(八)Lock锁
一、引言
线程并发的过程中,肯定会设计到一个变量共享的概念,那么我们在多线程运行过程中,怎么保证每个先拿获取的变量信息都是最新且有序的呢?这一篇我们来专门学习一下Lock锁。
我们先来了解几个概念:
乐观锁与悲观锁
悲观锁:
假定会发生并发冲突,即共享资源会被某个线程更改。所以当某个线程获取共享资源时,会阻止别的线程获取共享资源。也称独占锁或者互斥锁,例如java中的synchronized同步锁。
乐观锁:
假设不会发生并发冲突,只有在最后更新共享资源的时候会判断一下在此期间有没有别的线程修改了这个共享资源。如果发生冲突就重试,直到没有冲突,更新成功。CAS就是一种乐观锁实现方式。
PS:CAS相关知识戳这里~
公平锁与非公平锁
- 公平锁的实现就是谁等待时间最长,谁就先获取锁
- 非公平锁就是随机获取的过程,谁运气好,cpu时间片轮询到哪个线程,哪个线程就能获取锁
可重入锁与不可重入锁
不可重入锁
若当前线程执行中已经获取了锁,如果再次获取该锁时,就会获取不到被阻塞。
可重入锁
每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。
二、Condition
在使用Lock之前,我们使用的最多的同步方式应该是synchronized关键字来实现同步方式了。配合Object的wait()、notify()系列方法可以实现线程的等待/通知模式。
PS:Condition的实质是通过控制线程的等待和唤醒来达到控制指定线程的功能。
特点:
- 依赖于Lock对象,调用Lock对象的newCondition()对象创建而来
- 可以实现等待/通知形式的线程交互模式
- 可以有选择性的进行线程通知,唤醒指定线程
基本方法:
public interface Condition {
void await() throws InterruptedException;
boolean await(long var1, TimeUnit var3) throws InterruptedException;
long awaitNanos(long var1) throws InterruptedException;
void awaitUninterruptibly();
boolean awaitUntil(Date var1) throws InterruptedException;
void signal();
void signalAll();
}
- await() :造成当前线程在接到信号或被中断之前一直处于等待状态。
- await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
- awaitNanos(long nanosTimeout) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout - 消耗时间,如果返回值 <= 0 ,则可以认定它已经超时了。
- awaitUninterruptibly() :造成当前线程在接到信号之前一直处于等待状态。【注意:该方法对中断不敏感】。
- awaitUntil(Date deadline) :造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false。
- signal() :唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。
- signalAll() :唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。
使用举例:
/**
* Condition使用范例
*
* @author 有梦想的肥宅
*/
public class ConditionTest { //1、创建一个Lock对象,Condition的使用需要依赖Lock对象
public Lock lock = new ReentrantLock();
//2、使用Lock对象的newCondition()方法来生成Condition对象
public Condition condition = lock.newCondition(); //3、main方法测试Condition的作用
public static void main(String[] args) {
ConditionTest conditionTest = new ConditionTest();
//3.1、构造一个容量为2的固定线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
//3.2、执行conditionWait()方法使线程进入“等待”状态
executorService.execute(new Runnable() {
@Override
public void run() {
conditionTest.conditionWait();
}
});
//3.3、conditionSignal()方法“唤醒”线程
executorService.execute(new Runnable() {
@Override
public void run() {
conditionTest.conditionSignal();
}
});
} /**
* 线程等待
*/
public void conditionWait() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "拿到锁了");
System.out.println(Thread.currentThread().getName() + "等待信号");
condition.await();//线程进入等待状态,不进入finally语句块进行锁的释放,要等待被唤醒
System.out.println(Thread.currentThread().getName() + "拿到信号");
} catch (Exception e) { } finally {
lock.unlock();
}
} /**
* 线程唤醒
*/
public void conditionSignal() {
lock.lock();
try {
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "拿到锁了");
condition.signal();//唤醒线程
System.out.println(Thread.currentThread().getName() + "发出信号");
} catch (Exception e) { } finally {
lock.unlock();
}
} }
三、ReentrantLock可重入锁
ReentrantLock:是一个可重入锁,且它可以设置自身为非公平锁或者是公平锁。
常用方法:
- ReentrantLock() : 创建一个ReentrantLock实例【默认非公平锁】
- lock() : 获得锁
- unlock() : 释放锁
/**
* ReentrantLock测试类
*
* @author 有梦想的肥宅
*/
public class ReentrantLockTest {
//全局对象lock【构造参数设置为true表示为公平锁,false或为空则默认是非公平锁】
private static Lock lock = new ReentrantLock(true); //线程方法
public static void test() {
for (int i = 0; i < 2; i++) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获取了锁");
TimeUnit.MILLISECONDS.sleep(1000);//等待1秒,为了更直观地观察公平锁的机制
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
} //运行方法
public static void main(String[] args) {
System.out.println("=====公平锁实例=====");
//启动一个名叫“线程A”的线程
new Thread("线程A") {
@Override
public void run() {
test();
}
}.start();
//启动一个名叫“线程B”的线程
new Thread("线程B") {
@Override
public void run() {
test();
}
}.start();
}
}


ReentrantLock与synchronized的比较
相似点
它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,等到释放掉锁或者唤醒后才能继续获得锁。
区别
1️⃣对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成
2️⃣便利性:Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。
3️⃣锁的细粒度和灵活度:ReenTrantLock优于Synchronized【可以指定在哪加锁和解锁】
四、ReentrantReadWriteLock可重入读写锁
定义:ReentrantReadWriteLock是一种可重入读写锁,内部有两把锁来实现读和写的锁功能,在ReentrantLock的基础上优化了性能,但是使用起来需要更加谨慎。
性质:
可重入
如果你了解过synchronized关键字,一定知道他的可重入性,可重入就是同一个线程可以重复加锁,每次加锁的时候count值加1,每次释放锁的时候count减1,直到count为0,其他的线程才可以再次获取。
读写分离
我们知道,对于一个数据,不管是几个线程同时读都不会出现任何问题,但是写就不一样了,几个线程对同一个数据进行更改就可能会出现数据不一致的问题,因此想出了一个方法就是对数据加锁,这时候出现了一个问题:线程写数据的时候加锁是为了确保数据的准确性,但是线程读数据的时候再加锁就会大大降低效率,这时候怎么办呢?那就对写数据和读数据分开,加上两把不同的锁,不仅保证了正确性,还能提高效率。
锁可以降级
线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性。

锁不可升级
线程获取读锁是不能直接升级为写入锁的。需要释放所有读取锁,才可获取写锁,我们理解了上面的概念之后,接下来我们看看如何去使用。

使用示例:
/**
* ReentrantReadWriteLock测试类【可重入读写锁】
*
* @author 有梦想的肥宅
*/
public class ReentrantReadWriteLockTest {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true);//全局可重入读写锁对象
private final Lock readLock = reentrantReadWriteLock.readLock();//读锁
private final Lock writeLock = reentrantReadWriteLock.writeLock();//写锁
private final List<String> data = new ArrayList<>();//模拟被操作的数据 /**
* 写数据的方法
* @Description 使用writeLock获取一把写锁,然后内部List写入数据,最后在finally中释放写锁。
*/
public void write() {
try {
//1、加上写锁
writeLock.lock();
//2、操作公共数据
data.add("写数据");
System.out.println("当前线程" + Thread.currentThread().getName() + "正在写数据");
//3、线程等待3秒
Thread.sleep(3000);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
//4、释放写锁
writeLock.unlock();
}
} /**
* 读数据的方法
* @Description 使用readLock获取一把读锁,然后内部List读取数据,最后再finally中释放读锁。
*/
public void read() {
try {
//1、加上写锁
writeLock.lock();
//2、读取公共数据
for (String str : data) {
System.out.println("当前线程" + Thread.currentThread().getName() + "正在读数据");
}
//3、线程等待3秒
Thread.sleep(3000);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
//4、释放读锁
readLock.unlock();
}
}
}
参考资料:
- JAVA Condition
- Condition
- Java并发之Condition
- 可重入锁详解(什么是可重入)
- ReentrantLock详解
- 可重入读写锁ReentrantReadWriteLock的使用详解
并发编程(八)Lock锁的更多相关文章
- [转载] java并发编程:Lock(线程锁)
作者:海子 原文链接: http://www.cnblogs.com/dolphin0520/p/3923167.html 出处:http://www.cnblogs.com/dolphin0520/ ...
- 并发编程 17—— Lock
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- 【多线程】Java并发编程:Lock(转载)
原文链接:http://www.cnblogs.com/dolphin0520/p/3923167.html Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized ...
- Java并发编程:Lock(转)
本文转自:http://www.cnblogs.com/dolphin0520/p/3923167.html Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized ...
- 5、Java并发编程:Lock
Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.l ...
- Java并发编程:Concurrent锁机制解析
Java并发编程:Concurrent锁机制解析 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: # ...
- 【java并发编程】Lock & Condition 协调同步生产消费
一.协调生产/消费的需求 本文内容主要想向大家介绍一下Lock结合Condition的使用方法,为了更好的理解Lock锁与Condition锁信号,我们来手写一个ArrayBlockingQueue. ...
- python 并发编程 多进程 互斥锁 目录
python 并发编程 多进程 互斥锁 模拟抢票 互斥锁与join区别
- 转: 【Java并发编程】之二十:并发新特性—Lock锁和条件变量(含代码)
简单使用Lock锁 Java5中引入了新的锁机制--Java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lock接 ...
- Python之路(第三十八篇) 并发编程:进程同步锁/互斥锁、信号量、事件、队列、生产者消费者模型
一.进程锁(同步锁/互斥锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 例 ...
随机推荐
- 2 Spark角色介绍及运行模式
第2章 Spark角色介绍及运行模式 2.1 集群角色 从物理部署层面上来看,Spark主要分为两种类型的节点,Master节点和Worker节点:Master节点主要运行集群管理器的中心化部分,所承 ...
- 团队作业5:Alpha版本测试和发布(歪瑞古德小队)
目录 一.项目文档和代码 二.Alpha版本测试报告 2.1 功能测试 2.1.1 功能列表 2.1.2 场景测试 2.1.3 测试结果 2.1.4 bug清单 2.2 兼容性测试 2.3 性能测试 ...
- python 向excel 插入图片
这是工作中一个真实的需求. 要做gt excel 表,表中要插入图片. 1.要把图片resize 基本相同的大小. 2.通过一下脚本插入图片到excel #!/usr/bin/env python3 ...
- 谷歌分析(GA)新版的有哪些改变
http://www.wocaoseo.com/thread-221-1-1.html 最近GA做了两次大规模改版,修改了GA使用率最高的traffic source.content面板以及最核心的a ...
- 关键词seo优化的核心和重点
http://www.wocaoseo.com/thread-197-1-1.html 网站SEO优化是很多站长所必须要面对的问题,但是很多新手站长对关键词应如何选择,关键词要如何布局一 ...
- Vue基础(一)---- 模板语法
1.基本理解 Vue其实是一个渐进式JavaScript框架,封装好了一些方法,不再需要操作通过操作DOM,在相同的目标下能够更快的编写代码. 声明式渲染→组件系统→客户端路由→集中式状态管理→项目构 ...
- Clock()函数简单使用(C库函数)
Clock()函数简单使用(转) 存在于标准库<time.h> 描述 C 库函数 clock_t clock(void) 返回程序执行起(一般为程序的开头),处理器时钟所使用的时间.为了获 ...
- 攻防世界——Misc新手练习区解题总结<1>(1-4题)
第一题this_if_flag: 第一题就不多说了,题目上就给出了flag复制粘贴就可以了 第二题pdf: 下载附件后,得到如下界面 Ctrl+a全选文字,复制出来看看是什么,粘贴后恰好得到flag ...
- Codeforces1312D Count the Arrays 组合数学
题意 给你\(n\)和\(m\),问满足以下条件的数列的个数: 数列长度为\(n\) 数列值域范围为\(\left[1,m\right]\) 数列有且仅有一对相等的数 数列是单峰数列(先严格递增后严格 ...
- ZT:C/C++ 字符串与数字相互转换
转载地址:https://www.cnblogs.com/happygirl-zjj/p/4633789.html 一.利用stringstream类 1. 字符串到整数 stringstre ...