synchronized 关键字 - 监视器锁monitor lock
创始人
2024-06-02 21:28:50
0

请添加图片描述

✨个人主页:bit me👇
✨当前专栏:Java EE初阶👇
✨每日一语:迷雾散尽后,天光大亮,我看清了远处的灯塔,奔走在漫漫时光中,褪去青涩,我终将成为我故事里的主角。

目 录

  • 🍓一. synchronized 的特性
  • 🍑二. synchronized 使用示例
  • 🍇三. Java 标准库中的线程安全类

 

🍓一. synchronized 的特性

synchronized 从字面意思上是 “同步” 指的是 “互斥”。

“同步” 和 “异步” 在一起讨论又是不一样的意思

 
例如去餐馆吃饭

  • 同步:老板把饭做好,我在前台等着然后自己打包带走。(调用者自己来负责获取到调用结果)
  • 异步:老板把饭做好,我在椅子上做好等着老板端到我面前。(调用者自己不负责获取调用结果,是由被调用者把算好的结果主动推送过来)

synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待

  • 进入 synchronized 修饰的代码块, 相当于 加锁
  • 退出 synchronized 修饰的代码块, 相当于 解锁

在这里插入图片描述

LOCK 这个指令是存在互斥的,当 t1 线程进入 LOCK 之后,t2 也尝试 LOCK ,t2 的 LOCK 就不会直接成功。t2 执行 LOCK 的时候发现 t1 已经加上锁了,t2 此处无法完成 LOCK 操作,就会阻塞等待(BLOCKED),要阻塞等到 t1 把锁释放(UNLOCK),当 t1 释放锁之后,t2 才有可能获取到锁(从 LOCK 中返回,并且继续往下执行),t2 到底能不能拿到锁得看有多少竞争者,竞争者是指已经进入了 LOCK 指令,进入 BLOCKED 状态的线程,才是竞争者!!!

在加锁的情况下,线程执行的三个指令就被岔开了,岔开之后,就能保证一个线程 sava 之后,另一个线程才 load ,于是此时计算结果就准了

刷新内存:

synchronized 的工作过程:

  1. 获得互斥锁
  2. 从主内存拷贝变量的最新副本到工作的内存
  3. 执行代码
  4. 将更改后的共享变量的值刷新到主内存
  5. 释放互斥锁

 

🍑二. synchronized 使用示例

1. 直接修饰普通方法: 锁的 SynchronizedDemo 对象

public class SynchronizedDemo {public synchronized void methond() {//...}
}

2. 修饰静态方法: 锁的 SynchronizedDemo 类的对象

public class SynchronizedDemo {public synchronized static void method() {//...}
}

3. 修饰代码块: 明确指定锁哪个对象

  • 锁当前对象(可以创建出多个实例)
public class SynchronizedDemo {public void method() {synchronized (this) {//...}}
}

() 里的 this 指的是,是针对哪个对象进行加锁!加锁操作是针对一个对象来进行的,相当于是针对 this 来进行加锁

举例上厕所,滑稽老铁是线程,厕所门锁是要锁的对象

在这里插入图片描述

由上可知多个线程去调用 method 方法的时候,其实就是在针对这个 this 指向的对象来加锁,此时如果一个线程获取到锁了,另外的线程就要阻塞等待,但是如果多个线程尝试对不同的对象加锁,则相互之间不会出现互斥的情况。

在 Java 中,任何一个对象,都可以作为锁的对象(都可以放在 synchronized 的括号中),在其他语言,如C++,Python…都是专门搞了一类特殊对象来用作加锁对象,大部分正常对象不能用来加锁。在 Java 中每个对象,内存空间中有一个特殊的区域,对象头(JVM 自带的,对象的一些特殊的信息)。一个对象分为对象头和对象的属性,对象头里是 JVM 自动添加的信息(其中就有和加锁相关的标记信息),对象的属性里是咱们自己写的代码的信息。

  • 锁类对象(整个 JVM 中只有一个)
synchronized static void func(){//...
}

JVM 加载类的时候就会读取 .class 文件,构造类对象在内存中,类名.class 的方式就能拿到这个类的类对象

无论是使用哪一种用法,使用 synchronized 的时候都是要明确锁对象! (明确是对哪个对象进行加锁)只有当两个线程针对同一个对象进行加锁的时候,才会发生竞争,如果是两个线程针对不同的线程进行加锁则没有竞争,因为想加的锁被别人获取到了,而产生的阻塞等待。

  • 总结一下第三条两者区别和联系:
public synchronized static void func1(){//...
}public static void func2(){synchronized (Counter.this){//...}
}

这两者写法视为是等价的!!!类对象是整个程序唯一的!这样加锁,但凡调用到 func1 和 func2 ,之间都会产生竞争

public synchronized void func3(){//...
}public void func4(){synchronized (this){//...}
}

这两者写法视为是等价的!!!此处加锁是针对 this,而 this 可以有多个!!

如:

Counter count1 = new Counter();
Counter count2 = new Counter();
count1.func4(); 和 count2.func4(); 不会产生竞争,这两个 func4 里面的 this 是不同的,一个是 count1,一个是 count2。

  • 写如下代码观察各种锁情况下的结果
public class Demo15 {public static Object locker1 = new Object();public static Object locker2 = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized (locker1){System.out.println("t1 start");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t1 finish");}});t1.start();Thread t2 = new Thread(()->{synchronized (locker2){System.out.println("t2 start");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2 finish");}});t2.start();}
}
  1. 两者使用不同的锁得出的结果

在这里插入图片描述
t1 在 finish 之前,t2 就能 start 说明两个 synchronized 之间没有产生竞争

  1. 两者使用同一把锁,只需要 synchronized 后面括号里的 this 改为一样即可,同为 locker1 或者 同为 locker2。

在这里插入图片描述
加上同一把锁之后,就可以看到 t1 执行完 finish 之后释放了锁,然后 t2 才进行 start,发生了锁竞争。

注意:

  • 即使先写了 t1.start ,后写了 t2.start ,不一定是 t1 先执行,t2 后执行!!start 操作是在系统内核里创建出线程(构造 PCB,加入链表里),具体这个线程的入口方法开始执行,还是要看系统的调度!!(t1 t2 执行顺序不确定)
  • 针对类对象加锁和当前对象加锁是基本没有区别的,有区别就应该是形式上不一样,因为加了 static 之后,形式就成为了 synchronized (类名.class),还是一样的结果。实例上也有区别,类对象只有唯一一个实例,只要是使用了它就会有互斥关系,普通的对象加锁,则是有多个实例。

 

🍇三. Java 标准库中的线程安全类

  1. Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, 又没有任何加锁措施.
  • ArrayList
  • LinkedList
  • HashMap
  • TreeMap
  • HashSet
  • TreeSet
  • StringBuilder
  1. 但是还有一些是线程安全的. 使用了一些锁机制来控制.
  • Vector (不推荐使用)
  • HashTable (不推荐使用)
  • ConcurrentHashMap(推荐使用)
  • StringBuffer

上面不推荐使用是因为把所有的关键方法都无脑加了 synchronized ,加锁的代价会牺牲很大的运行速度!!!加锁之后就容易产生阻塞等待,如 t1 加锁成功,t2 尝试加锁,就会进入阻塞等待的状态 (BLOCKED 状态) (t1 t2 并发不起来)。当 t1 解锁之后,t2 不一定能立即获取到锁,还得看操作系统具体的调度,一旦涉及到调度,要隔多久才会实现调度是不确定的!!

因为加锁涉及到了一些线程的阻塞等待和线程的调度,可以视为一旦使用了锁,就和 "高性能" 告别了。

ConcurrentHashMap 内部做了一系列的优化手段,来提高效率,所以推荐使用

  1. 还有的虽然没有加锁, 但是不涉及 “修改”, 仍然是线程安全的
  • String

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...