一:几种锁的概念

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. MySQL like查询使用索引

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

  2. Linux系列 -- XShell破解版安装教程

    目录 一.xshell6商业版安装教程 1. 为什么要用xshell 2. 打开Keygen软件获取注册码 3.安装Xmanager_PowerSuite软件 4.打开康康. 二.XShell远程连接 ...

  3. 剑指 Offer 20. 表示数值的字符串 + 有限状态自动机

    剑指 Offer 20. 表示数值的字符串 Offer 20 常规解法: 题目解题思路:需要注意几种情况: 输入的字符串前后可能有任意多个空格,这是合法的. 正负号: (1)正负号只能出现一次. (2 ...

  4. Semaphore实战

    简介 Semaphore信号量计数器.和CountDownLatch,CyclicBarrier类似,是多线程协作的工具类,相对于join,wait,notify方法使用起来简单高效.下面我们主要看看 ...

  5. mongoDB服务器连接不上Error: couldn't connect to server 127.0.0.1:27017, connection attempt failed: SocketException:

    一大早打开node项目就报错,终端报 UnhandledPromiseRejectionWarning: MongooseServerSelectionError: connect ECONNREFU ...

  6. 常见 git 需求整理(持续更新中)

    首发于 语雀文档 突然感觉自己对 git 还是挺熟悉的,因为团队里新来的七八号应届生来问我 git 问题,基本没有答不上的情况,但为了能更好地对知识进行整理,还是记录一下为好. (希望能)持续更新.. ...

  7. 从零学脚手架(三)---webpack属性详解

    如果此篇对您有所帮助,在此求一个star.项目地址: OrcasTeam/my-cli 在上一篇中,介绍了webpack的entry.output.plugins属性. 在这一篇,接着介绍其它配置属性 ...

  8. slickgrid ( nsunleo-slickgrid ) 4 解决区域选择和列选择冲突

    slickgrid ( nsunleo-slickgrid ) 3 解决区域选择和列选择冲突 之前启用区域选择的时候,又启用了列选择(CheckboxSelectColumn),此时发现选择状态与区域 ...

  9. POJ_2752 Seek the Name, Seek the Fame 【KMP】

    一.题目 POJ2752 二.分析 比较明显的KMP运用. 但是这题不是只找一个,仔细看题后可以发现相当于是在找到最大的满足条件的后缀后,再在这个后缀里面找满足条件的后缀. 可以不断的运用KMP得出答 ...

  10. python-类的多态的理解

    了解多态 多态指的是一类事物有多种形态 .定义:多态是一中使用对象的方式,更容易编写出通用的代码,做出通用的编程,一适应需求的不断变化 实现步骤: 1.定义父类,并提供公共方法 2.定义子类,并重写父 ...