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/: 重入锁死与死锁和嵌套管程锁死非常相似.锁和读写锁两篇文章中都有涉及到重入锁死的问题. 当一个线程重新获取锁,读写 ...
随机推荐
- 状压DP小拼盘
有的DP题,某一部分的状态只有两种,选或不选. 开数组记录,代价太大,转移不方便. 状态压缩意为,用 “0/1“ 表示 “选/不选“ . 把状态表示为二进制整数. There are 10 kinds ...
- 云服务器——之Linux下安装nginx
第一步:下载 Nginx,下载地址:http://nginx.org/download/nginx-1.6.2.tar.gz 第二步:安装nginx需要安装的一些环境: 1.例如: yum insta ...
- 在dataframe添加1行(首行,或者尾部),且不覆盖
如果直接用下面的代码添加第1行,则会覆盖掉原来的第1行. #指定位置增加一行: df.loc[0]={'a':1,'b':2} 正确方法: 新建一个同样的 dataframe, 然后合并两个dataf ...
- IP不是万能药 为何有蜘蛛侠等大片的索尼要放弃电影
为何有蜘蛛侠等大片的索尼要放弃电影"> 近年来,国内狂炒"IP"这一概念,比如动漫.网络文学.小说.游戏等,甚至围绕IP制造出内容矩阵.从一个IP延伸至多个领域 ...
- 如何在自己的CSDN博客中增添【高大上】的博客栏目?
前几天看到过一位博主的博客界面,向下看 ☟ (博主对不起啊!把你的公众号给抹了~~~),感觉做这个东西挺好玩的,而且我竟然找不到在哪个地方可以设置!在百度上也没有搜到教程,最后问了一下贺老师知道了入口 ...
- webpack插件
插件 plugins:[ new ExtractTextPlugin.extrct({ }) //创建html new HtmlWebpackPlugin({ title:"first pa ...
- XML转换
找到两个不错的c#的关于XML转string和将string格式化XML输出 感谢以下两位的分享: [string格式化XML输出]http://blog.csdn.net/a497785609/ar ...
- CSS 加载动画
CSS加载动画 实现加载动画效果,需要的两个关键步骤: 1.做出环形外观 border:16px solid #f3f3f3; border-radius:50%; border-top:16px s ...
- Vue 项目分环境打包
我们开发项目的时候,用vue-cli 2.x版本新建的项目,只有dev, pro两种开发环境, 有时需要个test环境来给测试使用,所以找了很多方法,总结了个最简单的方法来给大家使用 packa ...
- SQLyog试用到期的解决方法(仅供个人学习使用,禁止转载或用于商业盈利)
作者:EzrealYi 本章链接:https://www.cnblogs.com/ezrealyi/p/12434105.html win+r->输入regedit->进入注册表 在计算机 ...