一:几种锁的概念

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. where & having 关键字

    where和having都是做条件筛选的 where执行的时间比having要早 where后面不能出现组函数 having后面可以出现组函数 where语句要跟在from后面 ,where 不能单独 ...

  2. Vue学习笔记-vue-element-admin 按装报错再按装

    一  使用环境 开发系统: windows 后端IDE: PyCharm 前端IDE: VSCode 数据库: msyql,navicat 编程语言: python3.7  (Windows x86- ...

  3. Java流程控制:选择结构

    一.选择结构 选择结构用于判断给定的条件,根据判断的结果来控制程序的流程. Java中选择结构的语法主要分为'if...else'语句和'switch...case'语句. Java中选择结构语句在语 ...

  4. 正则表达式匹配${key}并在Java中使用

    1.正则表达式匹配${key} \$\{([a-z]+)\} 能够匹配字符串中以${key}形式的文本(其中key为小写应为字母) .*\$\{([a-z]+)\}.* 可以用来检测文本中是否有${k ...

  5. 阻塞队列——四组API

    方式 抛出异常 有返回值,不抛出异常 阻塞等待 超时等待 添加 add() offer() put() offer(...) 移除 remove() poll() take() poll(...) 检 ...

  6. Kubernetes-1.概述

    内容主要摘自官网文档资料 官方地址 概述Kubernetes基本信息 前提条件: 掌握容器或Docker知识 文档编写基于kubernetes v1.17版本 目录 概述 Kubernetes对象 K ...

  7. Wireguard 全互联模式(full mesh)配置指南

    上篇文章给大家介绍了如何使用 wg-gen-web 来方便快捷地管理 WireGuard 的配置和秘钥,文末埋了两个坑:一个是 WireGuard 的全互联模式(full mesh),另一个是使用 W ...

  8. nignx的location正则匹配

    原文链接:http://nginx.org/en/docs/http/ngx_http_core_module.html Syntax: location [ = | ~ | ~* | ^~ ] ur ...

  9. 剑指 Offer 62. 圆圈中最后剩下的数字 + 约瑟夫环问题

    剑指 Offer 62. 圆圈中最后剩下的数字 Offer_62 题目描述 方法一:使用链表模拟 这种方法是暴力方法,时间复杂度为O(nm),在本题中数据量过大会超时. 方法二:递归方法 packag ...

  10. POJ-3159(差分约束+Dijikstra算法+Vector优化+向前星优化+java快速输入输出)

    Candies POJ-3159 这里是图论的一个应用,也就是差分约束.通过差分约束变换出一个图,再使用Dijikstra算法的链表优化形式而不是vector形式(否则超时). #include< ...