一:几种锁的概念

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. vscode 配置表

    { "git.ignoreMissingGitWarning": true, "editor.multiCursorModifier": "ctrlC ...

  2. 开源OA办公平台搭建教程:O2OA+Arduino实现物联网应用(二)

    O2OA平台搭建 O2OA的开发环境非常简单,安装服务器后即可通过浏览器进行开发了和使用.具体可参考文档库中的其他文档,有比较详细的介绍,这里就不再赘述了. Arduino开发发环境搭建 安装Ardu ...

  3. MySQL like查询使用索引

    在使用msyql进行模糊查询的时候,很自然的会用到like语句,通常情况下,在数据量小的时候,不容易看出查询的效率,但在数据量达到百万级,千万级的时候,查询的效率就很容易显现出来.这个时候查询的效率就 ...

  4. wxWidgets源码分析(9) - wxString

    目录 wxString wxString的中文字符支持 Windows Linux Unicode Linux UTF-8 总结 wxString与通用字符串的转换 wxString对象的创建 将wx ...

  5. 解决tui-editor布局错误失效问题(Vue-Element-Admin)

    更新:突然想起来会不会是兼容或者版本问题? 使用tui-editor遇到markdown预览样式错误,也不清楚是不是版本的问题,改了下源码之后效果正常了 打开控制台看到几个类名不对劲,te-edito ...

  6. Django 报错 Reverse for 'content' not found. 'content' is not a valid view function or pattern name.

    Django 报错 Reverse for 'content' not found. 'content' is not a valid view function or pattern name. 我 ...

  7. C# webapi跨域

    C# webapi跨域   第一种在Web.config中<system.webServer>节点中配置(不支持多个域名跨域) 1 <httpProtocol> 2 <c ...

  8. 漏洞复现-CVE-2017-4971-Spring Web Flow 远程代码执行

            0x00 实验环境 攻击机:Win 10 靶机也可作为攻击机:Ubuntu18 (docker搭建的vulhub靶场)(兼顾反弹shell的攻击机) 0x01 影响版本 Spring ...

  9. ElasticSearch 进阶

    目录 ElasticSearch 进阶 SearchAPI 检索信息 Query DSL 基本语法格式 查询-match 查询-match_phrase 查询-multi_match 查询-bool复 ...

  10. C# 应用 - 多线程 1) 多线程的知识图谱