Synchronized

news2024/11/24 18:25:31

Synchronized

  • 前言
  • 1.synchronized修饰符和线程控制
    • 1.1synchronized修饰符和Object Monitor模式
    • 1.2 synchronized修饰符可标注的位置
  • 2. 方法
    • 2.1 wait
      • wait多态表达式
      • notify()方法和notifyAII()方法
    • interrupt中断信号
      • 判断是否收到interrupt中断信号的方法

前言

悲观锁在Java中有两种典型的实现方式,一种是基于Object Monitor模式的资源操作方式,另一种是基于AQS技术的资源操作方式。他们共同的特点需要操作者获得资源的独占操作权才能对相关资源进行操作。

补充:乐观锁思想

  1. 该思想假设,并不是任何时候都有两个或多个操作者同时操作相同的资源,从而产生操作冲突。
  2. 该思想还假设,即使操作结果存在错误也没有关系。可以通过对比预期值和实际值来确认操作的正误,如果出现错误,则放弃本次操作,重新操作即可。
  3. 基于这种思想,操作者无须获得资源的操作独占权,也无须等待其他操作者释放资源的独占操作权。简单来说,这是一种无须加锁的,带有并发操作性的思想。在操作结束后,操作者只需比较实际操作结果是否符合预期操作结果,如果不符合,则放弃本次操作并重试。这种线程安全性的操作思想称为乐观锁思想
  4. Java中的乐观锁思想通常基于CAS(Compare and Swap,比较与交换)技术实现,但CAS进行比较的判定依据及比较后是否要进行重试,往往由操作者自行决定。所以在Java中,基于乐观锁工作的工具类都存在类似于for(;;)或while(true)的源码结构,这并不是BUG,而是为了匹配乐观锁的实现思想。例如,在ConcurrentHashMap集合中,基于乐观锁思想添加数据对象的源码片段如下。
    在这里插入图片描述

1.synchronized修饰符和线程控制

  1. 不同的操作系统(Windows、UNIX、Linux等)支持的线程底层实现和操作效果不同。不过操作系统支持的线程状态至少可以归为四类,分别为就绪、执行、阻塞和结束,这四种状态可以互相切换。
  2. 在创建线程时,操作系统不会为它分配独立的资源。一个应用程序(进程)中的所有线程,都可以共享这个应用程序(进程)中的资源,如这个应用程序的CPU资源、I/O资源、内存资源。

在这里插入图片描述

1.1synchronized修饰符和Object Monitor模式

  1. Thread.stop()、Thread.suspend()方法在JDK 1.2后,被官方弃用。
    如果要在异常情况下中断线程的运行(或通知线程进入中断逻辑),则需要向线程发出interrupt中断信号,下面会讲解interrupt中断信号的使用过程。
  2. 上图展示了java中的部分方法如何对线程间的切换施加影响。这些方法都和synchronized修饰符有关,后者代表一种经典的线程控制模型–Object Monitor模式。
  3. Object Monitor模式是一种典型的悲观锁实现,使用Java对象模型中的特定区域对线程状态、对象状态的描述进行线程操作。下面看一下在这种模式下的线程操作实例,代码如下。
    在这里插入图片描述
    在Object Monitor模式下,多个线程要对特定的对象进行操作,首先需要获取这个对象的独占操作权,然后进入synchronized修饰符对应的代码块(简称synchronized代码块)进行执行。未获得对象独占操作权的线程会在synchronized代码块外阻塞等待(可理解为一种临界状态),直到获取对象的独占操作权。当然,synchronized代码块内正在执行的线程也可以主动释放对象的独占操作权(如使用wait()方法),并且使自己进入阻塞状态,以便其他处于阻塞状态的线程重新抢占该对象的独占操作权。
    在这里插入图片描述
    上图中有两个线程,分别为线程1和线程2,这两个线程的执行过程都需要检查ThreadLock.WAIT_OBJECT对象的锁状态(检查独占操作权)。下面我们通过调试信息验证线程的执行过程。
    (1)线程1和线程2开始工作,这时两个线程都没有获得ThreadLock. WAIT_OBJECT对象(上图中的Object)的独占操作权(上图中提到的“锁”)。
    (2)线程1开始检查ThreadLock.WAIT_OBJECT对象的锁状态,发现没有任何线程占有这个对象的独占操作权,于是线程1通过改变ThreadLock.WAIT_OBJECT对象结构中特定区域的数据,获得了这个对象的独占操作权并进入synchronized代码块继续执行。
    (3)接着线程2开始检查ThreadLock.WAIT_OBJECT对象的锁状态,发现ThreadLock.WAIT_OBJECT对象的独占操作权已经被其他线程占据,于是在检查位阻塞等待。
    (4)线程1继续运行,直到synchronized代码块执行完毕,然后线程1释放ThreadLock.WAIT_OBJECT对象的独占操作权。线程2被通知ThreadLock.WAIT_OBJECT对象不再处于被独占状态,可以重新抢占该对象的独占操作权。如果线程2获取了ThreadLock.WAIT_OBJECT对象的独占操作权,就会解除阻塞状态,进入synchronized代码块继续执行(这之前线程2会处于阻塞状态)。

1.2 synchronized修饰符可标注的位置

可以在方法定义中添加synchronized修饰符,也可以在方法体中添加synchronized修饰符,还可以在static代码块中添加synchronized修饰符。synchronized修饰符的以下使用方法都是正确的。
在这里插入图片描述
不同位置的synchronized修饰符代表的意义不同。
在synchronized(){}语句中,可以在小括号中指定需要进行Object Monitor模式检查的对象。下面对以上几种synchronized修饰符的意义进行概要解释。

  1. 将synchronized修饰符加载在非静态方法上,其代表的意义和synchronized(this) { }语句代表的意义类似,即对拥有这个方法的对象进行Object Monitor模式下的锁状态检查。但两者的栈帧状态是有所区别的。
  2. 将synchronized修饰符加载在静态方法上,其代表的意义和synchronized (Class.class) { }语句代表的意义类似,即对拥有这个方法的类对象(类本身也是对象)进行Object Monitor模式下的锁状态检查。
  3. 在Object Monitor模式下需要关注对象的锁粒度。例如,基于synchronized (Class.class) { }语句的锁是不被推荐的,包括直接加在静态方法上的synchronized修饰符也不被推荐。因为控制粒度太过粗放,受影响的范围无法得到有效限制。

一个操作是否是线程安全的,需要开发人员真正理解synchronized修饰符在不同场景中的意义并正确使用,而不是盲目地使用synchronized修饰符。在两个线程的doOtherthing()方法中操作同一个对象NOWVALUE的示例代码如下,这段示例代码展示了synchronized修饰符的错误使用方法。
在这里插入图片描述

package com.company;

/**
 * @version 1.0
 * @date 2022/12/28
 */
public class SyncThread implements Runnable {
    private Integer value;

    /** 多个线程共同操作的整数值对象 */
    private static Integer NOWVALUE;

    public SyncThread(Integer value) {
        this.value = value;
    }

    /** 对这个类的实例化对象检查 */
    private synchronized void doOthering(){
        NOWVALUE = this.value;
        System.out.println("当前 NOWVALUE 的值为:" + NOWVALUE);
    }

    @Override
    public void run() {
        this.doOthering();
    }

    public static void main(String[] args) {
        Thread syncThread1 = new Thread(new SyncThread(10));
        Thread syncThread2 = new Thread(new SyncThread(100));
        syncThread1.start();
        syncThread2.start();
    }
}

从DEBUG的情况来看,可能发生静态对象NOWVALUE的值出现脏读的情况,输出结果如下。
在这里插入图片描述在这里插入图片描述
源码出现BUG的原因分析如下。

  1. syncThread1对象和syncThread2对象是SyncThread类的两个不同实例化对象。基于doOtherthing()方法的synchronized修饰符进行的锁状态检查,其目标对象并不是同一个对象。
  2. 如果读者要对SyncThread类的多个实例对象进行Object Monitor模式下的锁状态检查,那么应该对这个类的class对象进行Object Monitor模式下的锁状态检查。类似的语句应该是“privatesynchronized static void doOtherthing()”。

为了对SyncThread类的class对象进行锁状态检查,甚至无须在静态方法中标注synchronized修饰符,只需单独对SyncThread类的class对象标注synchronized修饰符。

package com.company;

/**
 * @version 1.0
 * @date 2022/12/28
 */
public class SyncThread implements Runnable {
    private Integer value;

    /**
     * 多个线程共同操作的整数值对象
     */
    private static Integer NOWVALUE;

    public SyncThread(Integer value) {
        this.value = value;
    }

    /**
     * 对这个类的实例化对象检查
     */
    private void doOthering() {
        synchronized (SyncThread.class){
            NOWVALUE = this.value;
            System.out.println("当前 NOWVALUE 的值为:" + NOWVALUE);
        }
    }

    @Override
    public void run() {
        this.doOthering();
    }

    public static void main(String[] args) {
        Thread syncThread1 = new Thread(new SyncThread(10));
        Thread syncThread2 = new Thread(new SyncThread(100));
        syncThread1.start();
        syncThread2.start();
    }
}

2. 方法

2.1 wait

在Object Monitor模式下,要确保一个对象是线程安全的,除了正确使用synchronized修饰符,还需要多种相关方法协助控制(线程间同步),以便实现复杂的控制逻辑。其中一组重要方法是wait()方法、notify()方法、notifyAll()方法。

  1. wait方法
    wait()方法由java.lang.Object类提供,该方法使用final修饰符进行修饰,代表不允许子类重载。wait()方法内部直接通过JNI调用JVM内核源码完成工作,即完成指定对象在Object Monitor模式下的状态改变。
    使用wait()方法可以使当前线程在获取某个对象独占操作权的情况下,主动释放该对象的独占操作权,并且将当前线程的状态切换为阻塞状态(WAITING状态)。
    这样,其他需要当前对象独占操作权且进入阻塞状态的线程,就可以重新抢占该对象的独占操作权了。

    对象的wait()方法只能在synchronized代码块中调用。如果没有这样做,就会抛出“IllegalMonitorStateException”异常。

在这个synchronized代码块中,调用wait()方法的对象必须是Object Monitor模式的检查对象。以下示例代码片段是错误的,因为synchronized代码块指定的进行Object Monitor模式检查的对象并不是当前调用wait()方法的对象。
在这里插入图片描述
在正常情况下,使用wait()方法进入阻塞状态的线程,会主动释放当前对象的独占操作权,如果要再次进入就绪状态,就必须重新获得当前对象的独占操作权。这个规则没有例外,即使在抛出异常时也没有例外,相应源码如下。
在这里插入图片描述
在上述源码片段中,线程在通过wait()方法主动释放currentObject对象的独占操作权后,就会进入阻塞状态。即使出于某种原因,该线程在阻塞状态收到了interrupt中断信号,也需要在重新获取该对象的独占操作权后,才能运行catch语句中的源码。

wait多态表达式

wait()方法的多态表达包括wait(long)和wait(long, int),这两种方法的具体解释如下。

  1. wait(long):阻塞一段时间(单位为毫秒),如果这段时间内没有收到notify信号、notifyAll信号,也没有收到interrupt中断信号,则重新允许该线程参与对象独占操作权的抢占工作。如果抢占成功,则该线程继续执行。
  2. wait(long, int):该方法和上述方法类似,但要注意第二个参数,该参数传入一个纳秒数(1毫秒=1000000纳秒,CPU一个指令大约需要2~4纳秒),这个入参所代表的纳秒数是在阻塞时间结束后,允许当前线程继续等待的1毫秒内的时间偏移量,它的取值范围为0~999999。
  3. 解除wait方法的阻塞状态
    有几种场景可以将使用wait()方法进入阻塞状态的线程状态重新切换为就绪状态。
    ・获得当前对象独占操作权的线程X在synchronized代码块中调用notify()方法或notifyAll()方法,并且当前线程重新获得对象的独占操作权。
    ・在使用wait()方法时指定一个最长的阻塞等待时间,在到时间后,当前线程会重新参与当前对象独占操作权的抢占工作,并且重新获取当前对象的独占操作权。
    ・当前线程收到interrupt中断信号,并且重新参与当前对象独占操作权的抢占工作。

notify()方法和notifyAII()方法

通过调用某个对象的notify()方法或notifyAll()方法,可以通知某个或所有因为没有该对象独占操作权而在Wait Set区域阻塞等待的线程(一般是主动调用wait()方法释放掉该对象独占操作权而进入阻塞状态的线程),可以重新参与该对象独占操作权的抢占工作了。两个方法的区别是,notify()方法会通知一个相关线程,notifyAll()方法会通知所有相关线程。

但从使用层面来看,这种工作方式具有一定的局限性。

在实际的并发场景中,程序员往往不能严格控制两个线程或多个线程的执行顺序。如果要控制两个线程或多个线程的执行顺序,则需要花费一定的设计成本。
例如,需要重新调整程序结构,或者引入其他线程,才能保证线程A调用wait()方法一定在线程B调用notify()方法/notifyAll()方法之前。本书后面会介绍一种无须严格限制两个同步线程的执行顺序,也能唤醒线程的方式。

interrupt中断信号

通过向一个指定的线程发送interrupt中断信号,可以通知这个线程进行中断,但是否要真的中断线程运行,或者在中断线程运行前是否要完成一些额外的工作,取决于程序员设计的具体处理逻辑。可以使用以下方法向指定线程发出一次interrupt中断信号(当然向自身线程发出interrupt中断信号也是可以的)。
在这里插入图片描述

判断是否收到interrupt中断信号的方法

  1. 线程在收到interrupt中断信号时可能处于就绪状态,也可能处于阻塞状态。如果线程处于就绪状态,则可以使用static boolean interrupted()方法或boolean isInterrupted()方法进行判断,如果interrupted()方法返回true,则表示当前线程收到了interrupt中断信号,线程中的相关逻辑可以根据实际情况决定是立即中断处理,还是继续处理,示例代码如下。
    在这里插入图片描述
    如果线程处于阻塞状态,则会抛出“java.lang.InterruptedException”异常,程序员可以在异常代码块中编写相关业务代码,决定是立即中断处理,还是继续处理,示例代码如下。
    在这里插入图片描述
    // TODO

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/126478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jenkins环境搭建

Jenkins环境搭建 一、Jenkins下载:Jenkins 点击Download,进入新的页面后点击下载Jenkins的war包,如下图: 注意:Jenkins是java语言开发,需要安装JDK并进行环境变量配置,Jenkins可以在Tomcat容器…

java注解(Annotation)和反射(Reflection)

文章目录重要的点一 注解(Annotation)(Annotation)(Annotation)1.1 注解初识1.2 内置注解1.3 内置注解代码演示1.4 元注解(meta−annotation)(meta-annotation)(meta−annotation)1.5 元注解代码演示1.6 自定义注解1.7 自定义注解代码演示二 反射(Reflection)(Reflection)(Refl…

gitlab-ci.yml关键字(三)before_script 、script 、after_script

before_script before_script 用于定义在每个作业执行之前所运行的一系列脚本命令。这里需要注意的是,before_script 运行的时机,是在制品(artifacts)恢复后,也就意味着,在这个时机中是可以操作制品的。 …

Unity 解决QFramework WebGL报错

Unity 解决QFramework WebGL报错🥝错误描述☕原因🍤 解决方案🥝错误描述 在新建工程中只导入了QFramework,Build WebGL(空场景) 会看到如下错误: Exception: Non-Public Constructor() not found! in QFramework.Saf…

点云 3D 目标检测 - SECOND(Sensors 2018)

点云 3D 目标检测 - SECOND(Sensors 2018)摘要1. 引言2. 相关工作2.1 基于前视图和图像的方法2.2 基于鸟瞰图的方法2.3 基于3D的方法2.4 基于融合的方法3. SECOND检测器3.1 网络架构3.1.1 点云分组3.1.2 Voxelwise特征提取器3.1.3 稀疏卷积中间提取器3.1…

代码模板3---基础算法(双指针算法/位运算/离散化/区间合并)

①双指针算法 一般做题:先用暴力做法,然后去看是否存在某种性质(如单调性,维护这个区间的单调性即可) AcWing 799. 最长连续不重复子序列 - AcWing AcWing 800. 数组元素的目标和 - AcWing AcWing 2816. 判断子序列 - …

期货交易软件哪个好?为什么选择期货MT4平台软件?

越来越多人选择期货投资,因为期货投资具有高投资高回报的特征。在做期货交易时,很多投资者会发现市场上有各种各样的期货交易软件。如果第一次接触期货交易,难免会不知道选择哪个期货交易软件更好。下面为大家讲讲期货交易软件哪个好&#xf…

centos7安装rabbitmq集群

公司号口项目让运维安装rabbitmq,结果rabbitmq页面state都显示down了,问运维居然说都正常,麻蛋欺负我无知,自己搞一遍试试。 前言 RabbitMQ是一个开源的强大的企业消息系统,支持主流的操作系统,支持多种开…

OpenHarmony#深入浅出学习eTs#(七)判断密码是否正确

本项目Gitee仓地址:深入浅出eTs学习: 带大家深入浅出学习eTs (gitee.com) 一、基本界面 本项目基于#深入浅出学习eTs#(四)登陆界面UI,继续进行,实现一个判断的功能 二、控件介绍 TextInput 可以输入单行文本并支持…

如果我是核酸系统架构师,我会这么用MQ。。。

V-xin:ruyuan0330 获得600页原创精品文章汇总PDF 目录 一、前情提示二、保证投递消息不丢失的confirm机制三、confirm机制的代码实现四、confirm机制投递消息的高延迟性五、高并发下如何投递消息才能不丢失六、消息中间件全链路100%数据不丢失能做到吗?…

医学图像包——DCMTK、VTK、ITK、RTK、SimpleITK

1.ITK-医学图像处理软件包 ITK( Insight Segmentation and Registration Toolkit)是美国国家卫生院下属的国立医学图书馆开发的一款医学图像处理软件包,是一个开源的、跨平台的影像分析扩展软件工具。 ITK的开发过程中采用了先进的多模态数…

VAE详解及PyTorch代码

三大有名的生成模型VAE、GAN以及Diffusion Model 其余两篇 看了网上的一些博客,大多都写到了重点,也就是后面的公式推导部分,可是大部分只有推导过程,很少有讲明白为什么要这么假设,我看的时候内心不断有个疑问&…

这篇文章详细介绍动态内存管理 ,让你醍醐灌顶【c语言】

文章目录动态内存函数mallocfreecallocrealloc常见的动态内存错误对NULL指针的解引用操作对动态开辟空间的越界访问对非动态开辟内存使用free释放使用free释放一块动态开辟内存的一部分对同一块动态内存多次释放动态开辟内存忘记释放(内存泄漏)练习柔性数…

物联网终端的信息保护

针对漏洞的恶意行为分析 我们共捕获到 4 种针对 UPnP 漏洞的利用行为 1,如表 4.7 所示。从中可以看出,这些漏洞均为远程 命令执行类漏洞。另外我们也发现,当漏洞出现在特定端口时,攻击者一般不会经过 UPnP 的发现阶段&#xff0c…

Moran指数分析

Moran指数分析 Moran指数(莫兰指数)是研究空间关系的一种相关系数值,比如研究中国31省市GDP之间是否具有空间相关关系。Moran指数通常分为两种,分别是全局Moran指数和local局部Moran指数。全局Moran指数用于分析整体上是否存在空…

概率分布到底有什么用?

1.1 要概率分布有什么用? 个人理解:每种概率分布对应描述了某种特定事件发生的规律,像是一个模板,只要某种事件符合该分布的要求,那么就可以用对应的概率分布计算此事件的概率 1.2 为什么非要确定一个分布?…

计算机组成原理复习题

一、选择题 一个节拍信号的宽度是指______。 A. 指令周期 B. 存储周期 C. 机器周期 D. 时钟周期 我的答案: D正确答案: D 3.3分 2. (单选题) 中断向量地址是______。 A. 子程序入口地址 B. 中断服务子程序出口地址 C. 中断返回地址 D. 中断服务子程序入口地址 我的答案: D正…

SpringCloudAlibaba、SpringCloud版本和SpringBoot版本适配

本文继SpringCloud版本和SpringBoot版本适配后,加入SpringCloudAlibaba组件版本适配! 官网链接:https://github.com/alibaba/spring-cloud-alibaba/wiki/

MySQL基本用法

一、数据库的基本操作: 1、启动数据库:net start mysql; 2、进入数据库:mysql -h localhost -u root -p; 3、关闭数据库服务:net stop mysql; 4、查看数据库:show databases; 5、新建数据库:create databa…

[vue element-ui]JAVA POST请求

01.前端 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title><script type"text/javascript" src"js/jquery-3.4.1.min.js"></script><script type"text/javascript&qu…