咀嚼Lock和Synchronized锁
1.Synchronized锁
底层是monitor
监视器,每一个对象再创建的时候都会常见一个monitor
监视器,在使用synchronized
代码块的时候,会在代码块的前后产生一个monitorEnter和monitorexit
指令,来标识这是一个同步代码块。
1.1 执行流程
线程遇到同步代码块,给这个对象monitor
对象加1
,当线程退出当前代码块以后,给这个对象的monitor
对象减一,如果monitor
指令的值为0
则当前线程释放锁。
1.2 反编译源码
同步代码块反编译
public void test01(){
synchronized (this){
int num = 1 ;
}
}
两次monitorexit
的作用是避免同步代码块无法跳出,因此存在两种,正常退出和异常退出
同步方法反编译
public synchronized void test01(){
int num = 1 ;
}
可以发现其没有在同步方法前后添加monitor
指令,但是在其底层实际上也是通过monitor
指令实现的,只不过相较于同步代码块来说,他是隐式的。
1.3 锁升级
在JDK1.5
的时候对于synchronzied
做了一系列优化操作,增加了诸如:偏向锁,轻量级锁,自旋锁,锁粗化,重量级锁的概念。
1.3.1 偏向锁
在一个线程在执行获取锁的时候,当前线程会在monitor
对象中存储指向该线程的ID。当线程再次进入的时候,不需要通过CAS的方法再来进行加锁或者解锁,而是检测偏向锁的ID是不是当前要进行的线程,如果是,直接进入。
偏向锁,适用于一个线程执行任务的情况
在JDK1.6
中,默认是开启的。可以通过-XX:-UseBiasedLocking=false
参数关闭偏向锁
1.3.2 轻量级锁
轻量级锁是指锁为偏向锁的时候,该锁被其他线程尝试获取,此时偏向锁升级为轻量级锁,其他线程会通过自旋的方式尝试获取锁,线程不会阻塞,从而提供性能
升级为轻量级锁的情况有两种:
- 关闭偏向锁
- 有多个线程竞争偏向锁的时候
具体实现:
线程进行代码块以后,如果同步对象锁状态为无锁的状态,虚拟机将首先在当前线程的栈帧中创建一个锁记录的空间。这个空间内存储了当前获取锁的对象。
使用情况:
两个线程的互相访问
1.3.3 重量级锁
在有超过2个线程访问同一把锁的时候,锁自动升级为重量级锁,也就是传统的synchronized
,此时其他未获取锁的线程会陷入等待状态,不可被中断。
由于依赖于monitor
指令,所以其消耗系统资源比较大
上面的三个阶段就是锁升级的过程
1.3.4 锁粗化
当在一个循环中,我们多次使用对同一个代码进行加锁,这个时候,JVM会自动实现锁粗化,即在循环外进行添加同步代码块。
代码案例:
锁粗化之前:
for (int i = 0; i < 10; i++) {
synchronized (LockBigDemo.class){
System.out.println();
}
}
锁粗化之后:
synchronized (LockBigDemo.class){
for (int i = 0; i < 10; i++) {
System.out.println();
}
}
本次关于synchronized
的底层原理没有以代码的方式展开,之后笔者会出一篇synchronized
底层原理剖析的文章
2. Lock锁
一个类级别的锁,需要手动释放锁。可以选择性的选择设置为公平锁或者不公平锁。等待线程可以被打断。
底层是基于AQS
+AOS
。AQS
类完成具体的加锁逻辑,AOS
保存获取锁的线程信息
2.1 ReentrantLock
我们以ReentrantLock
为例解析一下其加锁的过程。
2.1.1 lock方法
首先通过ReentrantLock
的构造方法的布尔值判断创建的锁是公平锁还是非公平锁。
假设现在创建的是非公平锁,他首先会判断锁有没有被获取,如果没有被获取,则直接获取锁;
如果锁已经被获取,执行一次自旋,尝试获取锁。
如果锁已经被获取,则将当前线程封装为AQS
队列的一个节点,然后判断当前节点的前驱节点是不是HEAD
节点,如果是,尝试获取锁;如果不是。则寻找一个安全点(线程状态位SIGNAL=-1
的节点)。
开始不断自旋。判断前节点是不是HEAD
节点,如果是获取锁,如果不是挂起。
源码解读:
- 非公平锁
lock
final void lock() {
//判断是否存在锁
if (compareAndSetState(0, 1))
//获取锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//非公平锁的自旋逻辑
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取锁状态
int c = getState();
//如果锁没被获取,获取锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//当前线程已经获取到了锁
else if (current == getExclusiveOwnerThread()) {
//线程进入次数增加
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//将线程封装为一个线程节点,传入锁模式,排他或者共享
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 获取尾节点
Node pred = tail;
//如果尾节点不为Null,直接将这个线程节点添加到队尾
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//为空,自旋设置尾节点
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//初始化
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//将头结点和尾结点都设置为当前节点
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
//尝试入队
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取节点的前驱节点,如果前驱节点为head节点,则尝试获取锁
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果不是,寻找安全位
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//前驱节点已经安全
if (ws == Node.SIGNAL)
return true;
//前驱节点不安全,寻找一个线程状态为`Signal`的节点作为前驱节点
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//否则直接设置这个前驱节点的线程等待状态值
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//中断线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
2.1.2 unlock方法
代码解读:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//尝试释放锁
if (tryRelease(arg)) {
//获取队列头元素,唤醒该线程节点,执行任务
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//判断是否为当前线程拥有锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//释放成功
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//唤醒下一个节点
if (s != null)
LockSupport.unpark(s.thread);
}
2.1.3 Node节点
/** 共享锁,读锁使用 */
static final Node SHARED = new Node();
/** 独占锁*/
static final Node EXCLUSIVE = null;
/** 不安全线程 */
static final int CANCELLED = 1;
/** 需要进行线程唤醒的线程 */
static final int SIGNAL = -1;
/**condition等待中 */
static final int CONDITION = -2;
//线程等待状态
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
3. Lock锁和Synchronized的区别
Lock
锁是API层面,synchronized
是CPU
源语级别的Lock
锁等待线程可以被中断,synchronized
等待线程不可以被中断Lock
锁可以指定公平锁和非公平锁,synchronized
只能为非公平锁Lock
锁需要主动释放锁,synchronized
执行完代码块以后自动释放锁
更多原创文章请关注笔者公众号@MakerStack
咀嚼Lock和Synchronized锁的更多相关文章
- Lock、Synchronized锁区别解析
上篇博文在讲解 ConcurrentHashMap 时说到 1.7 中 put 方法实现同步的方式是使用继承了 ReentrantLock 类的 segment 内部类调用 lock 方法实现的,而在 ...
- Java中的锁——Lock和synchronized
上一篇Java中的队列同步器AQS 一.Lock接口 1.Lock接口和synchronized内置锁 a)synchronized:Java提供的内置锁机制,Java中的每个对象都可以用作一个实现同 ...
- Java同步锁——lock与synchronized 的区别【转】
在网上看来很多关于同步锁的博文,记录下来方便以后阅读 一.Lock和synchronized有以下几点不同: 1)Lock是一个接口,而synchronized是Java中的关键字,synchroni ...
- 015-线程同步-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现
一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. syn ...
- Lock、synchronized和ReadWriteLock,StampedLock戳锁的区别和联系以及Condition
https://www.cnblogs.com/RunForLove/p/5543545.html 先来看一段代码,实现如下打印效果: 1 2 A 3 4 B 5 6 C 7 8 D 9 10 E 1 ...
- Synchronized和Lock, 以及自旋锁 Spin Lock, Ticket Spin Lock, MCS Spin Lock, CLH Spin Lock
Synchronized和Lock synchronized是一个关键字, Lock是一个接口, 对应有多种实现. 使用synchronized进行同步和使用Lock进行同步的区别 使用synchro ...
- Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)
多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念.进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线 ...
- Java并发指南4:Java中的锁 Lock和synchronized
Java中的锁机制及Lock类 锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消 ...
- java里的锁总结(synchronized隐式锁、Lock显式锁、volatile、CAS)
一.介绍 首先, java 的锁分为两类: 第一类是 synchronized 同步关键字,这个关键字属于隐式的锁,是 jvm 层面实现,使用的时候看不见: 第二类是在 jdk5 后增加的 Lock ...
随机推荐
- MLHPC 2018 | Aluminum: An Asynchronous, GPU-Aware Communication Library Optimized for Large-Scale Training of Deep Neural Networks on HPC Systems
这篇文章主要介绍了一个名为Aluminum通信库,在这个库中主要针对Allreduce做了一些关于计算通信重叠以及针对延迟的优化,以加速分布式深度学习训练过程. 分布式训练的通信需求 通信何时发生 一 ...
- for循环迭代可迭代对象
模仿for循环迭代可迭代对象,# for i in Iterable:# iterable >>> 迭代器.iterator# 可迭代对象 iterable# 迭代器.iterato ...
- 一文带你定制unittest测试用例的名称
在之前的文章中,我在之前的文章中提到过,这里呢,考虑后,感觉之前的写法不够优雅,于是乎呢,我自己抽空去研究了下,主要是新写方法,这样呢,以后的要使用的时候,可以直接去使用,而不是每次换个环境就要修改环 ...
- 多测师讲解内置函数 _format_高级讲师肖sir
#python中的格式化输出:format()# 和%号格式化输出一样,是%号的另外一种格式#1.不设置指定位置,按默认顺序 a ='{}'.format('hello','nihao','dajia ...
- CDH5部署三部曲之三:问题总结
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- GIT之分支管理
分支管理 一.分支推进 主分支 单线分支,随着代码的提交而形成的一条直线,HEAD 随着commit提交之后的节点移动而移动. 子分支 当切换到子分支的时候,HEAD 则指向子分支的节点. 在子分支上 ...
- day42 Pyhton 并发编程05
一.内容回顾 # 线程 # 正常的编程界: # 进程 # 计算机中最小的资源分配单位 # 数据隔离 # 进程可以独立存在 # 创建与销毁 还有切换 都慢 给操作系统压力大 # 线程 # 计算机中能被C ...
- Python3.7有什么新变化
https://docs.python.org/zh-cn/3/whatsnew/3.7.html
- 【传递闭包】HDU 2157 How many ways??
UPD:现在才发现本题是个传递闭包 题目内容 春天到了,HDU校园里开满了花,姹紫嫣红,非常美丽. 葱头是个爱花的人,看着校花校草竞相开放,漫步校园,心情也变得舒畅. 为了多看看这迷人的校园,葱头决定 ...
- Vue 父子组件通信入门
父组件向子组件传值 1.组件实例定义方式,注意:子组件一定要使用props属性来定义父组件传递过来的数据 <script type="text/javascript"> ...