Java锁的深度化--重入锁、读写锁、乐观锁、悲观锁
Java锁
锁一般来说用作资源控制,限制资源访问,防止在并发环境下造成数据错误
锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized(重量级) 和 ReentrantLock(轻量级)等等 ) 。这些已经写好提供的锁为我们开发提供了便利。
一、重入锁
重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁。
synchronized和ReentrantLock就是重入锁对应的实现
synchronized重量级的锁
ReentrantLock轻量级的锁 lock()代表加入锁 unlock()代表释放锁
1、不可重入锁
说明当没有释放该锁时。其他线程获取该锁会进行等待
MyLock:
package com.zn.lockTest; public class MyLock {
//标识锁是否可用 如果值为true代表当前有线程正在使用该锁,如果为false代表没有人用锁
private boolean isLocked=false;
//获取锁:加锁
public synchronized void lock() throws InterruptedException {
//判断当前该锁是否正在使用
while (isLocked){
wait();
}
//当前没有人使用情况下就占用该锁
isLocked=true;
}
//释放锁
public synchronized void unLock(){
//将当前锁资源释放
isLocked=false;
//唤起正在等待使用锁的线程
notify();
}
}
MyLockTest:
package com.zn.lockTest; public class MyLockTest {
MyLock myLock=new MyLock();
//A业务方法
public void print() throws InterruptedException {
//获取一把锁
myLock.lock();
System.out.println("print业务方法");
doAdd();
//释放锁
myLock.unLock();
} //B业务方法
public void doAdd() throws InterruptedException {
//获取一把锁
myLock.lock();
System.out.println("doAdd业务方法");
//释放锁
myLock.unLock();
} public static void main(String[] args) throws InterruptedException {
MyLockTest test=new MyLockTest();
test.print();
}
}
控制台效果:
2、synchronized可重入性
如果当前A持有一把锁,在A业务内部调用B,那么B也同样拥有这把锁的使用权限
MyLock:
package com.zn.lockTest; public class MyLock {
//标识锁是否可用 如果值为true代表当前有线程正在使用该锁,如果为false代表没有人用锁
private boolean isLocked=false;
//获取锁:加锁
public synchronized void lock() throws InterruptedException {
//判断当前该锁是否正在使用
while (isLocked){
wait();
}
//当前没有人使用情况下就占用该锁
isLocked=true;
}
//释放锁
public synchronized void unLock(){
//将当前锁资源释放
isLocked=false;
//唤起正在等待使用锁的线程
notify();
}
}
MyLockTest:
package com.zn.lockTest; public class MyLockTest {
//A业务方法
public synchronized void print() throws InterruptedException {
//获取了一把锁
System.out.println("print业务方法");
doAdd();
} //B业务方法
public synchronized void doAdd() throws InterruptedException {
System.out.println("doAdd业务方法");
//释放锁
} public static void main(String[] args) throws InterruptedException {
MyLockTest test=new MyLockTest();
test.print();
}
}
控制台效果:
3、ReentrantLock
同样具有可重入性
package com.zn.lockTest; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class MyLockTest { //ReentrantLock
//创建锁对象
Lock lock=new ReentrantLock();
//A业务方法
public void print() throws InterruptedException {
//获取了一把锁
lock.lock();
System.out.println("print业务方法");
doAdd();
//释放锁
lock.unlock();
} //B业务方法
public void doAdd() throws InterruptedException {
//获取了一把锁
lock.lock();
System.out.println("doAdd业务方法");
//释放锁
lock.unlock();
} public static void main(String[] args) throws InterruptedException {
MyLockTest test=new MyLockTest();
test.print();
}
}
控制台效果:
4、ReentrantLock底层
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
//默认非公平锁
sync = new NonfairSync();
} /**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
//如果为true代表公平锁,否则为非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
MyReentrantLock:
package com.zn.lockTest; public class MyReentrantLock {
//标识锁是否可用 如果值为true代表当前有线程正在使用该锁,如果为false代表没有人用锁
private boolean isLocked=false;
//当前线程
Thread lockedBy=null;
//加锁数量计数
Integer lockedCount=0;
//加锁
public synchronized void lock() throws InterruptedException {
//获取当前线程
Thread thread=Thread.currentThread();
//判断当前是否正在使用锁,如果正在使用则对比当前使用要使用锁的线程和之前使用锁的线程是否一致
//如果一致代表可以重入,继续使用锁,不会发生阻塞
//如果不一致代表当前不是一个线程,则等待
while (isLocked && thread!=lockedBy){
wait();
}
//占用锁
isLocked=true;
//计数+1
lockedCount++;
//赋值线程
lockedBy=thread;
}
//释放锁
public synchronized void unlock(){
//判断当前是否是用一个线程
if(Thread.currentThread()==this.lockedBy){
//锁使用计数器-1
lockedCount--;
//判断计数器是否为0,如果为0则释放锁,然后唤醒正在等待的线程
if(lockedCount==0){
isLocked=false;
notify();
}
}
}
}
二、读写锁
相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些。
假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(译者注:也就是说:读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题。
Java5在java.util.concurrent包中已经包含了读写锁。
package com.zn.lockTest; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLock {
//创建一个集合
static Map<String,String> map=new HashMap<String,String>();
//创建一个读写锁
static ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
//获取读锁
static Lock readLock=lock.readLock();
//获取写锁
static Lock writeLock=lock.writeLock();
//写操作
public Object put(String key,String value){
writeLock.lock();
try {
System.out.println("Write正在执行写操作~");
Thread.sleep(100);
String put = map.put(key, value);
System.out.println("Write写操作执行完毕~");
return put;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
writeLock.unlock();
}
return null; } //写操作
public Object get(String key){
readLock.lock();
try {
System.out.println("Read正在执行读操作~");
Thread.sleep(100);
String value = map.get(key);
System.out.println("Read读操作执行完毕~");
return value;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
}
return null; } public static void main(String[] args) {
ReadWriteLock lock=new ReadWriteLock();
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(()->{
try {
//写操作
lock.put(finalI +"","value"+finalI);
//读操作
System.out.println(lock.get(finalI+""));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
} }
}
控制台效果:
三、乐观锁
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
version方式:
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
核心SQL语句:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
CAS操作方式:
即compare and swap 或者 compare and set,涉及到三个操作数(V、E、N),数据所在的内存值(V),预期值(E),新值(N)。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
四、悲观锁
总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。
Java锁的深度化--重入锁、读写锁、乐观锁、悲观锁的更多相关文章
- 【Java并发工具类】StampedLock:比读写锁更快的锁
前言 ReadWriteLock适用于读多写少的场景,允许多个线程同时读取共享变量.但在读多写少的场景中,还有更快的技术方案.在Java 1.8中, 提供了StampedLock锁,它的性能就比读写锁 ...
- ReentrantReadWriteLock 可重入的读写锁
可重入:就是同一个线程可以重复加锁,可以对同一个锁加多次,每次释放的时候会释放一次锁,直到该线程加锁次数为0,这个线程才释放锁. 读写锁: 也就是读锁可以共享,多个线程可以同时拥有读锁,但是写锁却只能 ...
- Java并发-显式锁篇【可重入锁+读写锁】
作者:汤圆 个人博客:javalover.cc 前言 在前面并发的开篇,我们介绍过内置锁synchronized: 这节我们再介绍下显式锁Lock 显式锁包括:可重入锁ReentrantLock.读写 ...
- java并发包&线程池原理分析&锁的深度化
java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的, ...
- JAVA多线程(三) 线程池和锁的深度化
github演示代码地址:https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-brian-query-servic ...
- 25.Java锁的深度化
Java锁的深度化 悲观锁.乐观锁.排他锁 场景 当多个请求同时操作数据库时,首先将订单状态改为已支付,在金额加上200,在同时并发场景查询条件下,会造成重复通知. SQL: Update 悲观锁与乐 ...
- Java多线程之ReentrantLock重入锁简介与使用教程
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6543947.html 我们知道,线程安全问题需要通过线程之间的同步来解决,而同步大多使用syncrhoize ...
- 【学习】005 线程池原理分析&锁的深度化
线程池 什么是线程池 Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序 都可以使用线程池.在开发过程中,合理地使用线程池能够带来3个好处. 第一:降低资源消耗.通过重复 ...
- 23、Java并发性和多线程-重入锁死
以下内容转自http://ifeve.com/reentrance-lockout/: 重入锁死与死锁和嵌套管程锁死非常相似.锁和读写锁两篇文章中都有涉及到重入锁死的问题. 当一个线程重新获取锁,读写 ...
随机推荐
- 读书笔记之 数字图像处理的MATLAB实现(第2版)
- 吴裕雄--天生自然 人工智能机器学习实战代码:线性判断分析LINEARDISCRIMINANTANALYSIS
import numpy as np import matplotlib.pyplot as plt from matplotlib import cm from mpl_toolkits.mplot ...
- Django学习之路03
django项目生命周期 路由层 路由匹配 #urls中的urlpatterns #url()方法 urlpatterns = [ url(r'^admin/', admin.site.urls), ...
- 2020 倒计时 1 天,Python 工程师找工作更难了?
Python 是最神奇的编程语言. 无意引战,我说的是"神奇",不是"最好",并不想去"撼动" PHP 的地位. ...
- MySQL5.7主从复制slave报Last_Errno: 1146错误解决
前提:由于slave磁盘未及时扩容原因导致磁盘即将写满,为了不影响业务将slave实例里一个10G的库drop了(项目前期建的库,数据现在已不使用了),然后又drop了master上的该库(对于大库建 ...
- Tp5安全篇入门
输入安全 设置public目录为唯一对外访问目录,不能把资源文件放入到应用目录: 使用框架提供的请求变量获取方法(Request类的param方法及input助手函数)而不是原生系统变量获取用户输入的 ...
- CoreGraphic
public func UIGraphicsBeginImageContextWithOptions( size: CGSize, opaque: Bool, _ scale: CGFloat) si ...
- Mysql5.7.25安装步骤
安装步骤 在官网下载mysql-5.7.25-winx64.zip压缩包到本地,解压到非中文目录. 列如(D:\Program Files\mysql-5.7.25-winx64). 在环境变量中添加 ...
- tp5.1 请求时间格式化
当前时间:{$Request.time|date='Y-m-d H:i:s'} 注意database.php的配置!记录一下!
- LeetCode 题解 | 1. 两数之和
题目描述: 给定一个整数数组和一个目标值,找出数组中和为目标值的两个数. 你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用. 示例: 给定 nums = [2, 7, 11, 15], t ...