并发编程(八)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之路(第三十八篇) 并发编程:进程同步锁/互斥锁、信号量、事件、队列、生产者消费者模型
一.进程锁(同步锁/互斥锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 例 ...
随机推荐
- Go 切片的一种有趣内存泄漏方式
今天我在看 Prashant Varanasi 的 Go 发布会演讲:使用火焰图进行生产分析(Analyzing production using Flamegraphs),在演讲开始的第 28 分钟 ...
- ent orm笔记2---schema使用(上)
在上一篇关于快速使用ent orm的笔记中,我们再最开始使用entc init User 创建schema,在ent orm 中的schema 其实就是数据库模型,在schema中我们可以通过Fiel ...
- Linux系统下部署项目流程
一.系统架构 linux系统 centOS 6.9 应用服务器:Tomcat /JDK 数据库服务器:MySQL 二.连接远程工具FinalShell 1.Centos 6: 启动服务:service ...
- CSS3动画之animation-timing-function中的stepshan shu
1.概念 animation-timing-function是规定动画的速度曲线,一般使用的是cubic-bezier() 控制动画曲线的,属性值一般有ease/ease-in/ease-out等,而 ...
- Windows 远程桌面鼠标光标不可见
一.问题描述 通过在云端的主机上部署 frp 服务,实现「使用Windows 远程桌面(RDP)从互联网侧访问内网的主机」.但是,使用 Windows 自带的远程桌面工具 RDP 连接到另一台计算机时 ...
- py_创建文件以及写入读取数据+异常处理
import readline import math import json ''' A: 第一行 第二行 第三行 ''' #从文件读取数据 with open("D:\A.txt&quo ...
- 关于提高服务器的带宽策略bonding
一:bonding的概念 所谓bonding就是将多块网卡绑定同一IP地址对外提供服务,可以实现网卡的带宽扩容.高可用或者负载均衡. 二:bonding的优势 1 网络负载均衡 2 提高带宽网络传输效 ...
- 理解Django 中Call Stack 机制的小Demo
1.工作流程 request/response模式下,request并不是直接到达view方法,view方法也不是将返回的response直接发送给浏览器的,而是request由外到里的层层通过各种m ...
- Labview学习之路(八)如何让控件显示在修饰符的前面
在Labview2017版本中,前面板选择修饰控件,会出现部分修饰控件会掩盖其他控件,情况如下: 我们右键点击和属性中都没有相关属性的改变,为什么是这样我也不清除: 上网查了一下,看到其他版本会有显示 ...
- 利用分块传输吊打所有WAF--学习笔记
在看了bypassword的<在HTTP协议层面绕过WAF>之后,想起了之前做过的一些研究,所以写个简单的短文来补充一下文章里“分块传输”部分没提到的两个技巧. 技巧1 使用注释扰乱分块数 ...