一:几种锁的概念

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锁由浅入深解析的更多相关文章

  1. Lock、Synchronized锁区别解析

    上篇博文在讲解 ConcurrentHashMap 时说到 1.7 中 put 方法实现同步的方式是使用继承了 ReentrantLock 类的 segment 内部类调用 lock 方法实现的,而在 ...

  2. 并发编程之synchronized锁(一)

    一.设计同步器的意义 多线程编程中,有可能会出现多个线程同时访问同一个共享.可变资源的情况,这个资源我们称之其为临界资源:这种资源可能是:对象.变量.文件等. 共享:资源可以由多个线程同时访问 可变: ...

  3. synchronized互斥锁实例解析

    目录 synchronized互斥锁实例解析 1.互斥锁基础使用:防止多个线程同时访问对象的synchronized方法. 1.1.多个线程调用同一个方法 1.2.多个线程多个锁,升级为类锁 2.线程 ...

  4. 基于synchronized锁的深度解析

    1. 问题引入 小伙伴们都接触过线程,也都会使用线程,今天我们要讲的是线程安全相关的内容,在这之前我们先来看一个简单的代码案例. 代码案例: /** * @url: i-code.online * @ ...

  5. 【从刷面试题到构建知识体系】Java底层-synchronized锁-2偏向锁篇

    上一篇通过构建金字塔结构,来从不同的角度,由浅入深的对synchronized关键字做了介绍, 快速跳转:https://www.cnblogs.com/xyang/p/11631866.html 本 ...

  6. Java并发之synchronized关键字深度解析(二)

    前言 本文继续[Java并发之synchronized关键字深度解析(一)]一文而来,着重介绍synchronized几种锁的特性. 一.对象头结构及锁状态标识 synchronized关键字是如何实 ...

  7. 咀嚼Lock和Synchronized锁

    1.Synchronized锁 底层是monitor监视器,每一个对象再创建的时候都会常见一个monitor监视器,在使用synchronized代码块的时候,会在代码块的前后产生一个monitorE ...

  8. synchronized锁详解

    synchronized的意义 解决了Java共享内存模型带来的线程安全问题: 如:两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?(针对这个问题进行分析 ...

  9. synchronized锁重入

    package synLockIn_1; /* synchronized锁重入,当一个线程得到一个对象锁且还未释放锁时,再次请求此对象锁时可以再次得到该对象的锁 * 此例中线程1进入Service类的 ...

随机推荐

  1. 微信小程序(二十)-UI组件(Vant Weapp)-02使用

    1.按钮使用 https://vant-contrib.gitee.io/vant-weapp/#/button 1.全局引入,在app.json中引入组件 "usingComponents ...

  2. python爬取股票最新数据并用excel绘制树状图

    大家好,最近大A的白马股们简直 跌妈不认,作为重仓了抱团白马股基金的养鸡少年,每日那是一个以泪洗面啊. 不过从金融界最近一个交易日的大盘云图来看,其实很多中小股还是红色滴,绿的都是白马股们. 以下截图 ...

  3. MySQL注入 前端int型参数插入SQL语句

    类似PHP语言的 mysql_real_escape_string() 的函数,在用来防范SQL注入的时候,可能会遇到int型注入成功的情况. mysql_real_escape_string()用法 ...

  4. (报错解决)Exception encountered during context initialization

    转: (报错解决)Exception encountered during context initialization 关键词 JavaEE JavaWeb eclipse XML AspectJ ...

  5. JS table排序

    <html lang="en"> <head> <meta charset="UTF-8"> <meta http-e ...

  6. 锐捷RG-UAC统一上网行为管理审计系统账号密码泄露漏洞 CNVD-2021-14536

    一:产品介绍: 锐捷 RG-UAC 统一上网行为管理审计系统 锐捷统一上网行为管理与审计RG-UAC系列是星网锐捷网络有限公司自主研发的上网行为管理与审计产品,以路由.透明.旁路或混合模式部署在网络的 ...

  7. 性能追击:万字长文30+图揭秘8大主流服务器程序线程模型 | Node.js,Apache,Nginx,Netty,Redis,Tomcat,MySQL,Zuul

    本文为<高性能网络编程游记>的第六篇"性能追击:万字长文30+图揭秘8大主流服务器程序线程模型". 最近拍的照片比较少,不知道配什么图好,于是自己画了一个,凑合着用,让 ...

  8. 键盘--扫描码--ASCII码--显示器上的字符

    在上一篇,我讲了键盘操作会产生扫描码以及如何解析Pause键和Print Screen键的扫描码. 在这一篇,我会说清楚"键盘上的输入为什么会出现在显示器上". 极简版 我们敲击键 ...

  9. Flutter教程- Dart语言规范-知识点整理

    Flutter教程- Dart语言知识点整理 Dart语言简介 Dart语言介绍 ① 注释的方式 ② 变量的声明 ③ 字符串的声明和使用 ④ 集合变量的声明 ⑤ 数字的处理 ⑥ 循环的格式 ⑦ 抛异常 ...

  10. python 常用的库

    本节大纲: 模块介绍 time &datetime模块 random os sys shutil json & picle shelve xml处理 yaml处理 configpars ...