并发编程(八)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之路(第三十八篇) 并发编程:进程同步锁/互斥锁、信号量、事件、队列、生产者消费者模型
一.进程锁(同步锁/互斥锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 例 ...
随机推荐
- 「完整案例」基于Socket开发TCP传输客户端
1 程序界面设计 TCP客户端在上位机开发中应用很广,大多数情况下,上位机软件都是作为一个TCP客户端来与PLC或其他服务器进行通信的.TCP客户端的主要功能就是连接服务器.发送数据.接收数据.断开 ...
- ovs 删除流表 指定 actions 中字段
例: ovs-ofctl del-flows br-int in_port=100,out_group=100 -O openflow13 ovs-ofctl del-flows br-int in_ ...
- AltiumDesigner画图不求人11 | 提高AD20启动速度的方法七选择手动释放工程 | 视频教程 | 你问我答
往期文章目录 AD画图不求人1 | AD20软件安装视频教程 | 含软件安装包 AD画图不求人2 | 中英文版本切换 AD画图不求人3 | 高亮模式设置 AD画图不求人4 | 双击设计文件无法启动Al ...
- classmethod与staticmethod
1.classmethod @classmethod # 把一个对象绑定的方法 修改成一个 类方法第一,在方法中仍然可以引用类中的静态变量第二,可以不用实例化对象,就直接用类名在外部调用这个方法什 ...
- python编程中的并发------多线程threading模块
任务例子:喝水.吃饭动作需要耗时1S 单任务:(耗时20s) for i in range(10): print('a正在喝水') time.sleep(1) print('a正在吃饭') time. ...
- Flink-1.10.0中的readTextFile解读
Flink-1.10.0中的readTextFile解读 最近在学习Flink,研究了一些东西,在准备自定义一个简单的监听文件的source作为练手的时候,遇到了一个问题.就是应该如何在自己的sour ...
- codeforce Round #665(div 2)A B C
A. Distance and Axis 题意:在一个0x轴上,给了a在0x轴上的坐标,要你放一个b点使得abs(0B - AB)的值等于 k,但是有的时候如果不移动A点就不能实现这个条件,所以要你求 ...
- 解决git add README.md 时报错 fatal: pathspec 'README.md' did not match any files
解决办法一: 直接在远程仓库创建然后在本地$ git pull origin master 解决办法二: 换成$ touch README.md在本地创建修改后再commit push上去
- ios Standard Framework和Umbrella Framework
Standard Framework:标准库,通过引用对应的header文件而不是引用master header 文件来引用类(也可以通过引用Master Header file来引用需要使用的类), ...
- windows-服务器-配置一个及多个-Apache-Tomcat
问题:如何在一台服务器上发布了几个Tomcat的系统???怎么配置环境变量?怎么设置Tomcat? 2020/8月更新,由于之前的java的环境变量有点绕,此次新加一个流程设计图 一.需求: 一台wi ...