synchronized锁由浅入深解析
一:几种锁的概念
1.1 自旋锁
自旋锁,当一个线程去获取锁时,如果发现锁已经被其他线程获取,就一直循环等待,然后不断的判断是否能够获取到锁,一直到获取到锁后才会退出循环。
1.2 乐观锁
乐观锁,是假设不会发生冲突,当去修改值的时候才判断是否和自己获得的值是一样的(CAS的实现,值也可以是版本号),如果一样就更新值,否则就再次去读取值,然后比较再更新。就是说每次去读数据的时候不会加锁,只有在更新数据的时候才去判断这个值或者版本号有没有被其他线程更新,所以说乐观锁适用于读操作比较多的场景。
1.3 悲观锁
悲观锁,是假设会有冲突发生,每次去读数据的时候,就会加锁,这样别的线程就获取不到锁,会一直阻塞直到锁被释放。synchronized就是悲观锁。
1.4 可重入锁/不可重入锁
顾名思义,可重入锁就是当线程拿到锁后,再没有释放锁之前,可以再次拿到锁进行操作,而不会出现死锁;不可重入锁,就是锁只能被拿一次,想要再次获得锁,只能在释放锁后再去获取。
可重入锁栗子如下:
//可重入锁
ReentrantLock lock = new ReentrantLock(); Thread thread = new Thread(new Runnable() {
@Override
public void run() {
i++;
lock.lock();
j++;
lock.lock();
i++;
System.out.println("i=== " + i + ";j==== " + j);
}
}); thread.start();
运行结果如下:
1.5 独享锁(写锁)/共享锁(读锁)
独享锁,也就是同时只能被一个线程拿到;共享锁,就是可以有多个线程同时获得锁,比如Semaphore就是共享锁。
1.6 公平锁/非公平锁
公平锁,当多个线程去拿锁的时候,如果是按照拿锁的顺序去获得锁的,那么就是公平锁;如果可以出现插队的情况,就是非公平锁。
二:synchronized解读
2.1 synchronized的使用
1:synchronized可以用在实例方法和静态方法上,是隐式使用。
2:synchronized可以用在代码块上,是显式使用。
3:synchronized锁是可重入锁、独享锁、悲观锁。
下面是具体实例:
public class SynchronizedDemo {
public static void main(String[] args) {
Counter counter1 = new Counter();
Counter counter2 = new Counter();
new Thread(new Runnable() {
@Override
public void run() {
// counter1.add();
Counter.staticAdd();
}
}).start(); new Thread(new Runnable() {
@Override
public void run() {
// counter2.add();
Counter.staticAdd();
}
}).start(); }
}
class Counter {
public static volatile int a;
//用在实例方法上,是synchronized(this)
public synchronized void add() {
System.out.println("线程:"+ Thread.currentThread().getName());
a++;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//用在静态方法上,是synchronized(Counter.class)
public synchronized static void staticAdd() {
System.out.println("线程:"+ Thread.currentThread().getName());
a++;
LockSupport.parkNanos(1000 * 1000 * 1000 * 2);
}
public void demo() {
//用在代码块上
synchronized (this) {
a++;
}
}
}
上面的例子,当synchronized用在实例方法上,其实就是对this加锁,也就是实例化的对象,当实例化多个对象时,其实就是加了多个锁,当在多个线程多个实例调用的时候,不会出现阻塞;synchronized用在静态方法上,其实就是对类对象进行加锁。
2.2 锁消除
锁消除是JIT在编译的时候做的优化,当在单线程情况下,加锁解锁会造成CPU性能的消耗,而且单线程中,也不需要加锁,所以JIT编译优化做了锁消除,即就是没有锁。
//锁消除,在单线程情况下,JIT编译会对此做优化,避免加锁解锁造成的CPU性能消耗
public static void main(String[] args) {
// StringBuilder builder = new StringBuilder(); //线程不安全
StringBuffer buffer = new StringBuffer(); //线程安全
buffer.append("a");
buffer.append("b");
buffer.append("c");
System.out.println(buffer);
} @Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
上述代码可以看到,stringBuffer的append源码中加了synchronized,当在单线程中,这个synchronized就会被消除掉,这是JIT编译时做的事情。
2.3 锁粗化
锁粗化是JIT编译时做的优化,我们平时在编码中也可以做些优化。
//锁粗化,JIT编译时优化
public static void main(String[] args) { for (int i=0; i<10; i++) {
synchronized (LockCoarse.class) {
a++;
}
}
//进行了优化
synchronized (LockCoarse.class) {
for (int i=0; i<10; i++) {
a++;
}
} //------------------------------- synchronized (LockCoarse.class) {
a++;
} synchronized (LockCoarse.class) {
a++;
} synchronized (LockCoarse.class) {
a++;
} //进行了优化
synchronized (LockCoarse.class) {
a++;
a++;
a++;
}
}
2.4 synchronized深度解析
思考:
1:synchronized加锁后,状态是如何记录的呢?
2:synchronized加锁的状态记录在什么地方呢?
3:synchronized加锁让线程挂起,解锁后唤醒其他线程,是如何做的?
JVM中有线程共享的区域:java方法区和堆内存,堆内存中存的是实例化的对象,对象内存中除了存字段的信息外,还会有一个对象头:
根据上面的图片可以看到,对象头中的信息有:
1:class meta address,就是指向方法区内的,类的元信息。
2:array length,是当对象是数组对象时,记录数组的长度的。
3:mark word,记录的是锁的信息,即锁的状态、锁的类型等。
mark word详解:
当一开始没有线程拿锁时,mark word中记录的是无锁信息,如下图:
偏向锁:
当在单线程中,一个线程去拿锁后,这个时候就是偏向锁,内存中会记录当前线程的id,这个时候就相当于无锁了,因为单线程中,加锁解锁会造成CPU性能的消耗,JIT会做优化;只有当另外的线程过来拿锁,发现线程ID和自己的不一样时,这个时候锁就会升级为轻量级锁。(JDK1.6之后默认偏向锁是开启的,可以在JVM优化里去关闭)
轻量级锁:
轻量级锁,当多个线程去拿锁(用CAS的方式去拿),若有线程成功拿到锁,另外的线程就会自旋,不停地尝试去获取锁,而且自旋的次数有限制,当达到最大的自旋次数后,锁就会升级为重量级锁。
上述图,假设线程1和线程2都去获取锁,这个时候假设线程1拿到了锁,那么线程2就会一直自旋,循环的去尝试获取锁;当自旋到一定的次数后,锁就会升级为重量级锁。
local thread address记录的就是线程的地址,00指的是这是一个轻量级锁。
重量级锁:
在每个对象中,都会有一个monitor监视器,假设T1线程和T2线程去拿重量级锁,如果T1拿到了锁,那么在monitor中会记录T1的地址,T2没有拿到锁,那么它会进入一个entryList集合,差不多就是等待队列,这个时候没有拿到锁的T2就不会一直自旋了。
上图中,可以看到,owner就是获得锁的线程的地址,它指向线程,EntryList存放的就是没有拿到锁的线程;当一个线程使用了wait方法使得自己挂起,因为wait只能在synchronized关键字中使用,那么当调用wait之后,会自动释放锁,这个时候调用了wait方法的线程会进入waitSet中,那么EntryList中的线程就有机会去拿锁,当有线程调用了notify或者notifyAll时,在waitSet中的线程会被唤醒,唤醒之后的线程会尝试去拿锁,拿不到会再次进入EntryList中;如果拿到锁的线程直接释放锁,那么它会离开monitor的监视。
锁升级过程:
无锁 ——》偏向锁 ——》轻量级锁 ——》重量级锁
当锁为重量级锁时,锁全部释放了,没有线程拿锁,会直接到无锁,偏向锁关闭状态,再次有线程拿锁时,会直接拿到重量级锁。
到此,整个锁的过程结束了,如有不足,万望谅解!
synchronized锁由浅入深解析的更多相关文章
- Lock、Synchronized锁区别解析
上篇博文在讲解 ConcurrentHashMap 时说到 1.7 中 put 方法实现同步的方式是使用继承了 ReentrantLock 类的 segment 内部类调用 lock 方法实现的,而在 ...
- 并发编程之synchronized锁(一)
一.设计同步器的意义 多线程编程中,有可能会出现多个线程同时访问同一个共享.可变资源的情况,这个资源我们称之其为临界资源:这种资源可能是:对象.变量.文件等. 共享:资源可以由多个线程同时访问 可变: ...
- synchronized互斥锁实例解析
目录 synchronized互斥锁实例解析 1.互斥锁基础使用:防止多个线程同时访问对象的synchronized方法. 1.1.多个线程调用同一个方法 1.2.多个线程多个锁,升级为类锁 2.线程 ...
- 基于synchronized锁的深度解析
1. 问题引入 小伙伴们都接触过线程,也都会使用线程,今天我们要讲的是线程安全相关的内容,在这之前我们先来看一个简单的代码案例. 代码案例: /** * @url: i-code.online * @ ...
- 【从刷面试题到构建知识体系】Java底层-synchronized锁-2偏向锁篇
上一篇通过构建金字塔结构,来从不同的角度,由浅入深的对synchronized关键字做了介绍, 快速跳转:https://www.cnblogs.com/xyang/p/11631866.html 本 ...
- Java并发之synchronized关键字深度解析(二)
前言 本文继续[Java并发之synchronized关键字深度解析(一)]一文而来,着重介绍synchronized几种锁的特性. 一.对象头结构及锁状态标识 synchronized关键字是如何实 ...
- 咀嚼Lock和Synchronized锁
1.Synchronized锁 底层是monitor监视器,每一个对象再创建的时候都会常见一个monitor监视器,在使用synchronized代码块的时候,会在代码块的前后产生一个monitorE ...
- synchronized锁详解
synchronized的意义 解决了Java共享内存模型带来的线程安全问题: 如:两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?(针对这个问题进行分析 ...
- synchronized锁重入
package synLockIn_1; /* synchronized锁重入,当一个线程得到一个对象锁且还未释放锁时,再次请求此对象锁时可以再次得到该对象的锁 * 此例中线程1进入Service类的 ...
随机推荐
- CSS绘制三角形和箭头
<html> <head> <meta charset="utf-8"> <title>CSS绘制三角形和箭头</title& ...
- java的read方法
public class RandomAccessDemo6 { public static void main(String[] args) throws IOException { RandomA ...
- 顶级c程序员之路 基础篇 - 第一章 关键字的深度理解 number-1
c语言有32个关键字,每个关键字你都理解吗? 今天出场的是: auto , register, static, extern 为什么他们会一起呢,说到这里不得不谈到c语言对变量的描述. c给每 ...
- 5G组网方案:NSA和SA
目录 5G组网的8个选项 独立组网(SA) 选项1 选项2 选项5 选项6 总结 非独立组网(NSA) 选项3系列 选项3 选项3a 选项3x 选项7系列 选项4系列 选项8 演进路线 5G组网的8个 ...
- Wireguard 全互联模式(full mesh)配置指南
上篇文章给大家介绍了如何使用 wg-gen-web 来方便快捷地管理 WireGuard 的配置和秘钥,文末埋了两个坑:一个是 WireGuard 的全互联模式(full mesh),另一个是使用 W ...
- 大话Spark(5)-三图详述Spark Standalone/Client/Cluster运行模式
之前在 大话Spark(2)里讲过Spark Yarn-Client的运行模式,有同学反馈与Cluster模式没有对比, 这里我重新整理了三张图分别看下Standalone,Yarn-Client 和 ...
- POJ-1182(经典带权并查集)
食物链 POJ-1182 一个很好的分析博客:https://blog.csdn.net/niushuai666/article/details/6981689 三种关系:两者同类,吃父节点,被父节点 ...
- 使用伪类(::before/::after)设置图标
使用伪类(::before/::after)设置文本前后图标.减少标签的浪费,使页面更加整洁. 如图: <!DOCTYPE html> <html> <head> ...
- frameset、frame和div 、iframe
框架一般应用于首页的界面排版工作.把一个网页切割成多个页面管理.frame文件一般只包含框架的布局信息,不会包含其他内容,所有的页面效果都是在各个frameset页面内显示.他们都从属于frame文件 ...
- java集合框架部分相关接口与类的介绍
集合基础 接口 Iterable //Implementing this interface allows an object to be the target of the "for-ea ...