Java并发编程(1)-线程安全基础入门知识

Java并发编程-基础篇 1. JVM与线程安全 可见性:当多个线程对一个线程进行操作的时候,其中一个线程修改了变量的值,而其他的线程并不知道该值已经被修改 可见性-synchronized JMM关于synchronized的两条规定: 1、线程解锁前,必须把共享变量的最新值刷新到主内存 2、线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁是同一把锁) 可见性-... 阅读详情


更多关于Java并发编程的文章请点击这里:Java并发编程实践(0)-目录页


Java并发编程第一篇博客,主要讲解线程的安全性,有无状态类是什么,以及原子性,原子操作,竞争操作,复合操作机制,最后讲解锁机制,使用内部锁以及内部锁的解读。
本文内容均总结自《Java并发编程实践》第二章 线程安全 的内容 ,详情可以查阅该书。

一、线程安全性

当多个线程同时访问一个类时,如果无需考虑这些线程在运行环境下的调度和交替执行,并且不需要进行额外的同步处理操作,这个类的执行结果仍然正确,那么可以称这个类的线程安全类。

1.1、无状态类

无状态类可以理解为在多个线程同时访问的情况下,每个线程都能得到正确的相应结果,因为无状态类中的变量和数据都是无状态(stateless)的,下面通过一个Servlet来说明什么是无状态类:

public class StateLessServlet extends HttpServlet{
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        String paramName = servletRequest.getParameter("paramName");
        servletResponse.getWriter().write(paramName);
    }
}

这个自定义的Servlet类做的事情很简单,即接受前台的一个特定的参数并且向前台响应,怎么判断这个类是不是无状态的类呢?我们可以设想有多个线程去访问这个类,无论是线程同步或者不同步访问,都会获得和输出特定的、正确的结果,所以这个类是一个典型的无状态类。其实绝大多数是Servlet都可以实现无状态,只有当该Servlet需要处理一些特定的请求并记录信息时,才会产生线程安全的需求。

所以,无状态类一定是线程安全类,它永远线程安全。

1.2、有状态类

有状态类,即包含了有状态变量或者有状态对象的类,这样的类一般是线程不安全类。以下自定义Servlet类可说明什么是有状态类。

public class StatefulServlet extends HttpServlet{
    //用于记录请求该Servlet的次数
    private long count  = 0;
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        String paramName = servletRequest.getParameter("paramName");
        count++;//请求次数加1
        servletResponse.getWriter().write(paramName+count);
    }
}

这个Servlet中有一个有状态量count,在单一线程下,这个类是安全的,因为在每次请求时,count都唯一且确定,请求后自增即可。但是在多线程情况下,这个类是线程不安全的,因为在线程同时访问时,这个有状态量count会出现错误,比如线程Thread1刚读到count为1时,线程Thread2此时进来也读到count为1,那么这两个线程的响应结果都为2,可是这明显不对,正确的结果是有一个线程应该响应结果为3,即该Servlet被访问了3次。
在这里插入图片描述
现在可以下一个小小的结论:
无状态类一定是线程安全的,而有状态类一般线程不安全

二、原子性

从上面的计数例子可以看出,每个线程对count这个变量的自增并不是一次操作完成的,而分成了三步:读-改-写,首先读取了count的当前值,然后再将其值加一,并且写入原先变量中,它并不是一个原子操作, 这就是导致了线程不安全的原因。

2.1、原子操作

所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,类似于MySql数据库中的事务操作:要不就完成该语句后Commit,中间出现错误就回滚(RollBack)。

2.2、竞争操作

用以下注册器的例子来讲解什么是竞争操作:

public class LazyInitRace(){
        //注册对象
        private RegisterObject instance = null;
        //获得注册对象
        public static RegisterObject getInstance(){
            if(instance == null){
                //注册对象为null
                return new RegisterObject();
            }
            //注册对象不为null
            return instance;
        }
    }

当有多个线程去请求这个类的getInstance()方法时,每个进程争夺注册对象的条件为instance是否为null,这就是线程的竞争条件,这就是一个竞争操作。竞争操作会引发线程的不安全,比如当进程Thread1和进程Thread2同时执行到getInstance,1看到instance是null,并且实例化一个新的注册对象。同时2也在检查instance是否为null,此时刻instance是否为null,这依赖于时序,是无法预期的。它包括调度的无常性,以及1初始化注册对象并设置instance域的耗时。如果2检查到instance为null,两个getInstance的调用者会得到不同的结果,然而,我们期望getInstance总是返回相同的实例,而不论线程的差异。上面的程序,又称为惰性初始化。

2.3、复合操作

在上面的计数器例子中,若count++这个自增是原子操作,那么就不会发生线程的不安全,那么每次自增都会产生预期的结果,即计数器准确地加一。这个读-改-写操作的全部执行过程可以看作是复合操作:为了保证线程的安全,必须让这一系列的复合操作原子地执行。

public class StatefulServlet extends HttpServlet{
    //使用concurent并发包中的atomic工具包下的原子变量类,保证多线程请求下的原子操作
    private AtomicLong count = new AtomicLong(0);
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        String paramName = servletRequest.getParameter("paramName");
        count.incrementAndGet();//自增1
        servletResponse.getWriter().write(paramName+count);
    }
}

java.util.concurrent是多线程开发常用的并发包,其中的atomic是并发包的原子变量工具类,使用它们代替基本变量或者对象,可以保证在多线程请求的情况下,每次都执行的是原子操作。

三、锁

先来看下这段用于缓存前台数字的查询,它一共有两个有状态量,但是这两个量都已经使用了原子变量(Atomic Variable)代替了,并且使用了原子变量的set、get方法,那么这段查询是线程安全的吗?

  public class SychronizedFactorizer extends  HttpServlet{
        private AtomicReference<Integer> cacheNumber = new AtomicReference<Integer>();
        private AtomicReference<List<Integer>> cacheNumbers = new AtomicReference<List<Integer>>();
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            String paramNumber = servletRequest.getParameter("paramNumber");
            if(Integer.parseInt(paramNumber) == cacheNumber.get()){
                //是最新的缓存
                servletResponse.getWriter().write(paramNumber);
            }else{
                //不是最新的缓存,将其替换成缓存
                cacheNumber.set(Integer.parseInt(paramNumber));
                //计入缓存集合中
                List<Integer> integers = cacheNumbers.get();
                //缓存数组尾部插入新缓存
                integers.add(Integer.parseInt(paramNumber));
            }
        }
    }

答案是这段缓存程序并不是线程安全的,因为线程和线程之间仍存在竞争操作,虽然每个set调用都是原子的,但是程序无法保证会同时更新cacheNumber和cacheNumbers;当某个线程只修改了cacheNumber而另一个变量还没开始修改的时候,其他线程将看到Servlet违反了不变约束,这样会形成一个程序漏洞,所以为了保护状态的一致性,要在单一的原子操作中更新相互关联的状态变量。

那么,应该如何让两个set量子操作合并为1个量子操作呢?这就涉及到代码块的量子操作,需要用锁(lock)来实现。

3.1、使用内部锁

Java提供了强制原子性的内部锁机制:synchronized块。一个锁对象有两部分,分别是对锁synchronized的引用,以及锁需要保护的代码块。当synchronized关键字放在方法声明时,那么表明对整个方法的代码进行强制原子性,当synchronized关键字单独使用时,表明是对{}中的代码块进行强制原子性,即上锁。

(1)对整个方法上锁
public synchronized void function(){...}
(2)对特定代码块上锁
synchronized(this){...}

上述的缓存代码,可以改造成这样:

ublic class SychronizedFactorizer extends  HttpServlet{
        private long cacheNumber = 0;
        private List<Integer> cacheNumbers = new ArrayList<Integer>();
        @Override
        public synchronized void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            String paramNumber = servletRequest.getParameter("paramNumber");
            if(Integer.parseInt(paramNumber) == cacheNumber){
                //是最新的缓存
                servletResponse.getWriter().write(paramNumber);
            }else{
                //不是最新的缓存,将其替换成缓存
                cacheNumber = Integer.parseInt(paramNumber);
                //缓存数组尾部插入新缓存
                cacheNumbers.add(Integer.parseInt(paramNumber));
            }
        }
    }
3.2、内部锁解读

执行线程进入synchronized块之前会自动获得锁(可以理解为获得了该段代码的控制权):而无论通过正常控制路径退出。还是从块中抛出异常,线程都会在放弃对synchronized块的控制时自动释放锁(可以理解为放弃对该段代码的控制权),从而能让其他线程去获得该锁。获得内部锁的唯一途径是:进入这个内部锁保护的同步块或方法。

在这里插入图片描述

内部锁在Java中扮演了互斥锁的角色,意味着至多只有一个线程可以拥有锁,如图所示,当线程Thread2尝试请求一个被线程Thread1占用的锁时,线程Thread2必须等待或者阻塞,直到Thread1释放锁,Thread2将永远等下去。

第 2 章 并发编程的其他基础知识 内置锁是排它锁, 也就是当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的。能和吞吐量要求的提高,出现了处理海量数据和请求的要求,这些都对高并发编程有着迫。存里面的变量复制到自己的工作空间或者叫作工作内存,线程读写变量时操作的是自己工。内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内。时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会。 阅读详情

相关推荐

Java并发编程,从线程安全到死锁避免的实战解析

Java并发编程的挑战在于正确处理共享资源、避免竞态条件和死锁,同时合理利用并发工具提高系统性能。通过本文的代码示例和解决方案,开发者可以掌握从基础线程安全到高级并发模式的完整知识体系。优先使用包提供的线程安全类合理配置线程池参数,避免资源耗尽使用工具监控并发程序的运行状态设计时尽量减少共享状态。

2401_82505179的博客 1199

Java 之 AtomicReference

写在前面: AtomicReference类提供了一个可以原子读写的对象引用变量。 原子意味着尝试更改相同AtomicReference的多个线程(例如,使用比较和交换操作)不会使AtomicReference最终达到不一致的状态。 AtomicReference甚至有一个先进的compareAndSet(object expect. object update)方法,它可以将引用与预期值(引用...

Bolg of EQuaker 788

【Java基础知识 1】Java入门级概述

优快云最强Java专栏,包含全部Java基础知识点、Java8新特性、Java集合、Java多线程、Java代码实例,理论结合实战,实现Java的轻松学习。

学Java,找哪吒 16万+

(七)原子类

原子类、Atomic

qq_32076957的博客 684

Arm Neoverse CMN-700 QoS与PrefetchTarget架构解析

在现代多核处理器系统中,内存访问性能优化是提升整体效能的关键。QoS(服务质量)技术通过优先级调控机制,确保关键任务获得更高的缓存带宽和内存访问权限。PrefetchTarget作为预取技术的一种实现,能够显著降低内存访问延迟,特别适用于AI训练和数据库系统等高性能计算场景。Arm Neoverse CMN-700通过RN SAM模块实现了硬件级的地址范围检测和优先级调控,支持最多8个独立内存区域的QoS配置。测试数据显示,合理配置QoS可使关键任务延迟降低40%,而PrefetchTarget技术则能减少

weixin_36197669的博客 316

获取上传至DMS服务器上文件的URL

如果需要失效时间延长会设置URL永久有效, 可以去修改函数SCMS_URL_GENERATE的参数DURATION 或EXPIRATION ,DURATION最大时间是999999,若需要永久有效,则需要设置EXPIRATION的时间,这时系统会忽略DURATION的参数,生成URL的时候 SCMS_URL_GENERATE会调用SCMS_URL_SIGN函数这是其中默认这是了URL的生效时间为两小时。

一只学习的渣渣 543

AtomicReference原子引用

说明 AtomicReference是作用是对”对象”进行原子操作。 提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。 AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,底层采用的是c...

chenwch 3405

Jetson Orin Nano@Ubuntu22.04:IMX219 CSI摄像头配置全流程与避坑指南

本文详细介绍了在Jetson Orin Nano@Ubuntu22.04系统上配置IMX219 CSI摄像头的全流程,包括硬件安装、驱动配置、常见问题排查及性能优化。特别强调了排线方向、驱动模式选择等关键细节,并提供了实用的命令和解决方案,帮助开发者高效完成摄像头部署与调试。

prometheus9mon的博客 321

无锁的对象引用:AtomicReference

AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。在介绍AtomicReference的同时,我希望同时提出一个有关原子操作的逻辑上的不足。 之前我们说过,线程判断被修改对象是否可以正确写入的条件是对象的当前值和期...

qq_25330791的博客 676

Comsol 管道导波裂纹检测:从建模到求解的完整流程解析

本文详细解析了使用Comsol Multiphysics进行管道导波裂纹检测的完整仿真流程。从几何建模、材料定义、物理场设置到网格划分与求解后处理,系统阐述了如何构建高保真虚拟模型以模拟导波与裂纹的相互作用。重点分享了激励信号设置、频散效应处理及参数化扫描等实战技巧,旨在帮助工程师与研究人员高效优化无损检测方案,降低实验成本。

weixin_29207439的博客 74

从入门到精通:dirmap 高级 Web 目录扫描实战指南

本文提供了一份从环境搭建到高级配置的dirmap Web目录扫描实战指南。详细解析了工具安装、核心配置文件dirmap.conf的深度调优,包括递归扫描、四种扫描模式(字典、爆破、爬虫、Fuzz)的实战应用,并演示了分阶段资产发现的完整工作流程,帮助安全人员高效发现隐藏目录与敏感文件。

rnn9storyteller的博客 482

什么时候需要AtomicReference?

问:既然在java中引用的赋值操作本身就是是原子的,那为什么还需要AtomicReference(原子引用)? 答:如果仅需要通过赋值操作改变一个引用,确实不需要AtomicReference。 // 注意volatile关键字 volatile Person person = new person("Jim"); public void processA() { // 赋...

抛弃幻想,准备斗争 7589

Unity 工具 之 ZXing QRCode 二维码的生成和识别(可设置生成不同颜色的/带图标的二维码)

Unity 基础 之 ZXing QRCode 二维码的生成和识别(可设置生成不同颜色的/带图标的二维码) 目录 Unity 基础 之 ZXing QRCode 二维码的生成和识别(可设置生成不同颜色的/带图标的二维码) 一、简单介绍 二、实现原理 三、注意实现 四、效果预览 五、ZXing.net ( zxing.unity.dll ) 插件下载 六、实现步骤 七、关键代码 一、简单介绍 Unity 开发中,自己整理的一些游戏开发可能用到的功能,整理归档,方便游...

仙魁XAN 9826

从两方面,推荐适合各个能力阶段学习的Java书籍

虽然视频学习资料是许多人入门或提升编程的首选,但是书籍材料对学习者来讲,也是必须要看的。尤其对于处在不同能力阶段的人来讲,选择适合自己学习的书尤为重要。那么,Java学习看什么书比较好呢?本文会按照基础入门和进阶提升两个方面,推荐适合各个能力阶段学习的书籍。 一、基础入门看什么书比较好? 1、《Java编程思想》 本书豆瓣评分9.1,足以看出编程学习者对它的追捧和青睐。这本书对初学者来讲比较好,一方面是因为书中详实的内容,既有Java语言基础语法又有Java高级特性。另一方面书中将最晦涩的概念,用最具亲和

xiaoxijing的博客 325

RK3588 部署 YOLO26 目标检测:从训练到 NPU 推理

如果做垂直场景(工业缺陷、农作物、特定零件),需要自己标数据。采集图像(500+ 张,覆盖不同光照/角度)→ 筛选(删除模糊/无目标)→ LabelImg 标注(选择 YOLO 格式)→ 划分(train:val:test = 7:2:1)labelImg├── train/├── val/├── test/YOLO26 在 RK3588 上的全流程是 PC 端训练 + ONNX 导出 → RKNN 转换(INT8 量化)→ 板端推理。

m0_71589036的博客 110

[QMT量化交易小白入门]-二十八、有了回测年化收益好的策略,如何在QMT量化实盘呢(附python代码解析)

本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写100篇左右。QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步,自己淋过雨了,希望大家都有一把伞。

木头大左的博客 2927

Java并发编程基础知识点

start当两个或多个线程互相等待对方释放资源时,就会发生死锁。例如:有两只狗秀逗和四眼,秀逗和它的好兄弟四眼都着急上厕所,假设上厕所需要拿到厕所钥匙和手纸,此时秀逗抢到了钥匙,四眼抢到了手纸。它俩谁也不让谁,秀逗拿着钥匙等待四眼给它手纸,四眼拿着手纸等秀逗给它钥匙。等着等着,两只狗都崩裤兜里了。。。这就是出现了线程死锁,反应到计算机上,可能的问题就是导致线程无法继续往下运行,还会导致计算机资源无法释放,系统可用资源逐渐减少,最终可能导致系统资源耗尽。参与死锁的线程无法继续执行,造成这些线程的永久阻塞。

落日的博客 1343

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Java并发编程基础(一篇入门)

1 并发编程简介 1.1 什么是并发编程 所谓并发编程是指在一台处理器上 “同时” 处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。 并发编程,从程序设计的角度来说,是希望通过某些机制让计算机可以在一个时间段内,执行多个任务。从计算机 CPU 硬件层面来说,是一个或多个物理 CPU 在多个程序之间多路复用,提高对计算机资源的利用率。从调度算法角度来说,当任务数量多于 CPU 的核数时,并发编程能够通过操作系统的任务调度算法,实现多个任务一起执行。 1.2 并发编程的重要性 对于一个

行者无疆的博客 5854

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

并发编程基础知识

读书笔记,作为自己的一个学习记录。

少不入川。的博客 1220

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Java并发编程第一篇(从零开始:一文读懂Java并发编程核心基础)

为什么需要用到并发?凡事总有好坏两面,这其中的权衡(trade-off)是什么,也就是说并发编程具有哪些缺点?以及在进行并发编程时,我们应该了解和掌握的核心概念又是什么?这篇文章将主要围绕这三个问题,为你揭开Java并发编程的神秘面纱。

Du_XiaoNan的博客 1045

Java并发编程面试题:基础(11题)

进程说简单点就是我们在电脑上启动的一个个应用,比如我们启动一个浏览器,就会启动了一个浏览器进程。进程是操作系统资源分配的最小单位,它包括了程序、数据和进程控制块等。线程是操作系统中调度的最小单位,它是进程中的独立执行单元。多个线程可以共享同一个进程的资源,如内存和文件句柄,但每个线程都有自己独立的栈和寄存器。与进程相比,线程的创建和上下文切换开销更小,因此在需要并发执行任务时,多线程是一种常用的解决方案。在编程中,多线程的典型应用包括并行处理、I/O 操作、并发服务器等场景。

在技术的广袤天地里,本博客如精准罗盘。剖析前沿科技,深掘代码奥秘,以精炼笔触,带您穿越复杂技术迷宫,速达知识彼岸。 1661

【从入门到放弃-Java】并发编程-线程安全

概述 并发编程,即多条线程在同一时间段内“同时”运行。 在多处理器系统已经普及的今天,多线程能发挥出其优势,如:一个8核cpu的服务器,如果只使用单线程的话,将有7个处理器被闲置,只能发挥出服务器八分之一的能力(忽略其它资源占用情况)。 同时,使用多线程,可以简化我们对复杂任务的处理逻辑,降低业务模型的复杂程度。 因此并发编程对于提高服务器的资源利用率、提高系统吞吐量、降低编码难度等方面起着...

阿里云云栖号 1062

关于并发编程与线程安全的思考与实践

使用多线程处理问题已经变得很普遍,但是对于多线程共同操作的对象必须使用线程安全的类。另外,还要搞清楚几个灵魂问题:(1)JMM的灵魂:Happens-before 原则(2)并发工具类的灵魂:volatile变量的读/写 和 CAS。

京东科技开发者 1019

Java 并发编程基础

Java 并发编程基础,涉及并发与并行的区别、进程与线程的区别、Java 线程基础、共享资源访问控制等内容。

leileiluoluo的博客 2177

Java并发编程核心知识详解

Java并发编程是一个复杂但强大的领域。理解这些核心概念和工具可以帮助开发者构建高效、可靠的并发应用程序。随着Java版本的更新,并发API也在不断演进(如CompletableFuture、Flow API等),开发者应持续关注新特性以编写更现代化的并发代码。

lyx20020823的博客 756

学习笔记:Java 并发编程①_基础知识入门

本博客主要介绍进程和线程的基本概念,Java线程中(基本操作,一一些主要的 api,线程状态,相关的应用和原理,两阶段终止模式。)

做笔记,记录学习中遇到的问题。 867
上一篇: 数据结构(7)-红黑树
下一篇: Java并发编程(2)-线程安全之共享对象解读
BoringRong
BoringRong 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄9年 2万+粉丝 386原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BoringRong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值