java多线程(并发)夯实之路-synchronized深入浅出

发布时间:2024年01月11日

使用锁的原因

线程安全

多个线程对共享资源进行写操作时,会发生上下文切换,造成线程安全问题。

如以下代码

上下文切换

发生上下文切换,如

临界区

一段代码块中如果存在对共享资源的多线程读写操作,则称这段代码区为临界区

以上代码临界区为

竞态条件Race Condition

多个线程在临界区内执行,代码执行序列不同导致结果无法预测,称为发生了竞态条件

synchronized解决方案

避免临界区的竞态条件发生:

阻塞式的解决方案:synchronized,Lock

非阻塞式的解决方案:原子变量

synchronized:俗称对象锁,它采用互斥的方式,使得同一时刻最多只能有一个线程持有对象锁,其他对象想要获取对象锁就只能进入阻塞状态(注意必须使用相同的锁)。

在加了锁的线程中,只有获取了锁的线程能够运行(其他线程进入BLOCKED阻塞状态,即使该线程时间片用完了,因为其他线程仍处于阻塞状态,所以该线程会重新获得时间片),synchronized内代码运行结束后线程会释放锁,其他阻塞的线程会被唤醒,然后去竞争锁,获取了锁的线程去运行……

这样保证了拥有锁的线程可以安全地执行临界区的代码,而不用担心发生上下文切换。

互斥同步都可以采用synchronized关键字来实现,但它们还是有区别的:

互斥是避免临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码

同步是由于线程执行的顺序不同,一个线程需要等待另一个线程执行到某一点

利用synchronized解决之前问题的代码如下:

synchronized实际上是用对象锁保证了临界区内代码的原子性,临界区内代码对外是不可分割的,不会被线程切换给打断。

用图来表示

面向对象的改进

把需要保护的共享变量放入一个类中

方法上的synchronized

synchronized修饰方法锁住的是this对象,synchronized? static修饰方法锁住的是class对象

不加synchronized的方法:好比不遵守规则的人,不老实排队(翻窗户进去)

Monitor

java对象头,以32位虚拟机为例

普通对象:Klass? Word指针指向它的class对象

数组对象:int为4字节,Integer为12字节(8字节对象头加上4字节的值)

Mark Word结构:倒数第三位表示是否为偏向锁,后两位表示加锁状态

Monitor(锁)被翻译为监视器或管程(操作系统层面)

每个java对象都可以关联一个Monitor对象,如果用synchronzied給对象上锁(重量级),该对象头的Mark Word中就会设置一个指针指向Monitor对象

Monitor只能有一个主人,一个线程成为了Monitor的Owner后,其他想要获取锁的线程会进入 Monitor的EntryList等待,进入BLOCKED状态。Monitor的Owner线程执行完毕后,Owner设为null,在 EntryLis中等待线程被唤醒,再去竞争锁,竞争结果由jdk底层实现决定。如下图所示。

注意synchronzied锁住同一个对象才会有同一个Monitor,不加synchronzied的对象不会关联监视

器。

刚开始Monitor中Owner为null
当Thread-2执行synchronized(obj)就会将Monitor的所有者Owner置为Thread-2,Monitor中只能有一个Owner
在Thread-2上锁的过程中,如果Thread-3,Thread-4,Thread-5也来执行synchronized(obj),就会进入EntryList BLOCKED
Thread-2执行完同步代码块的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争的时是非公平的图中WaitSet 中的Thread-0,Thread-1是之前获得过锁,但条件不满足进入WAITING状态的线程

synchronzied原理

对应的字节码为

如果6到16行出现异常,会到第19行,重置lock对象的Mark Word,唤醒EntryList,让其他线程去竞争锁,再抛出异常。synchronzied中的代码即使出现了异常也能解锁。

文章来源:https://blog.csdn.net/qq_61282807/article/details/135514145
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。