Java并发编程 对象的共享

使用MiniExcel.SaveAsByTemplate快速地保存百万数据 该代码展示了如何使用MiniExcel快速生成并导出百万级销售数据。核心方法GenerateLargeData()可生成指定数量的随机销售记录,包含客户名、日期、VIP状态、积分等20个字段。通过随机函数模拟真实数据分布,并支持自定义数据量(默认100万条)。配合Excel模板使用MiniExcel.SaveAsByTemplate()方法,能高效地将大数据量导出为Excel文件,且处理速度极快。代码结构清晰,包含数据生成、随机日期辅助方法和数据模型类,适合需要批量生成测试数据或导出大型报表的场景。 阅读详情

Java并发编程 对象的共享

1. 可见性

可见性是一种复杂的属性。在单线程环境中,如果向某个变量先写入值,然后在没有其他写入操作的情况下读取这个变量,那么总能得到相同的值。然而,当读操作和写操作在不同的线程中执行时,情况却并非如此。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。

下面代码说明了当多个线程在没有同步的情况下共享数据时出现的错误。在代码中,主线程和读线程都将访问共享变量 ready 和 number。主线程启动读线程,然后将 number 设为 42,并将 ready 设为 true。读线程一直循环直到发现ready的值变为 true,然后输出 number 的值。虽然 NoVisibility 看起来会输出42,但事实上很可能输出 0,或者根本无法终止。这是因为在代码中没有使用足够的同步机制,因此无法保证主线程写入的 ready 值和 number 值对于读线程来说是可见的。

// 在没有同步的情况下共享变量
public class NoVisibility {
    private static boolean ready;
    private static int number;
    
    private static class ReaderThread extends Threas {
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println(number);
        }
    }
    
    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}

NoVisibility 可能会持续循环下去,因为读线程可能永远都看不到 ready的 值。一种更奇怪的现象是,NoVisibility 可能会输出 0,因为读线程可能看到了写入 ready 的值,但却没有看到之后写入 number 的值,这种现象被称为“重排序(Reordering)”。只要在某个线程中无法检测到重排序情况(即使在其他线程中可以很明显地看到该线程中的重排序),那么就无法确保线程中的操作将按照程序中指定的顺序来执行。当主线程首先写入 number,然后在没有同步的情况下写入ready,那么读线程看到的顺序可能与写人的顺序完全相反。

在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。
在缺乏足够同步的多线程程序中,想要对内存操作的执行顺序进行判断,几乎无法得出正确的结论。

Volatile 变量

Java 语言提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile 类型的变量时总会返回最新写入的值。在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile 变量是一种比 sychronized 关键字更轻量级的同步机制。

volatile 变量对可见性的影响比 volatile 变量本身更为重要。当线程 A 首先写入一个 volatile 变量并且线程 B 随后读取该变量时,在写入 volatile 变量之前对 A 可见的所有变量的值,在 B 读取了 volatile 变量后,对B也是可见的。因此,从内存可见性的角度来看,写入 volatile 变量相当于退出同步代码块,而读取 volatile 变量就相当于进入同步代码块。然而,我们并不建议过度依赖 volatile 变量提供的可见性。如果在代码中依赖 volatile 变量来控制状态的可见性,通常比使用锁的代码更脆弱,也更难以理解。

仅当 volatile 变量能简化代码的实现以及对同步策略的验证时,才应该使用他们。
如果在验证正确性时需要对可见性进行复杂判断,那么就不要使用 volatile 变量。
volatile 变量的正确使用方式包括:确保他们自身状态的可见性,确保它们所引用对象的状态的可见性,
以及标识一些重要的程序生命周期事件的发生(初始化或关闭)。

下面程序是 volatile 一种典型的用法:检查某状态标记判断是否退出循环。线程试图通过类似于数绵羊的传统方法进入休眠状态。asleep 必须是 volatile 变量。否则,当 asleep 被另外一个线程修改时,执行判断的线程却发现不了。我们也可以用锁,但这会使得代码更加复杂。

volatile boolean asleep;
...
    while (!asleep) {
        countSomeSheep();
    }

虽然 volatile 变量很方便,但也存在一些局限性。volatile 变量通常用做某个操作完成、发生中断或者状态的标志。尽管 volatile 变量也可以用于表示其他的状态信息,但在使用时要非常小心。例如,volatile 的语义不足以确保递增操作(count++)的原子性,除非你能确保只有一个线程对变量执行写操作。

加锁机制既可以确保可见性又可以确保原则性,而 volatile 变量只能确保可见性

当且仅当满足以下所有条件时,才应该使用 volatile 变量:

  • 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值
  • 该变量不会与其他状态变量一起纳入不变性条件中
  • 在访问变量时不需要加锁

2. 发布与逸出

“发布(Publish)”一个对象的意思是指,使对象能够在当前作用域之外的代码中使用。例如,将一个指向该对象的引用保存到其他代码可以访问的地方,或者在某一个非私有的方法中返回该引用,或者将引用传递到其他类的方法中。在许多情况中,我们要确保对象及其内部状态不被发布。而在某些情况下,我们又需要发布某个对象,但如果在发布时要确保线程安全性,则可能需要同步。发布内部状态可能会破坏封装性,并使得程序难以维持不变性条件。例如,如果在对象构造完成之前就发布该对象,就会破坏线程安全性。当某个不应该发布的对象被发布时,这种情况就被称为逸出(Escape)。
发布对象的最简单方法是将对象的引用保存到一个公有的静态变量中,以便任何类和线程都能看见该对象,如下面程序所示。在 initialize 方法中实例化一个新的 HashSet 对象,并将对象的引用保存到 knownSecrets 中以发布该对象。

// 发布一个对象
public static Set<Secret> knownSecrets;
public void initialize() {
    knownSecrets = new HashSet<Secret>();
}

当发布某个对象时,可能会间接地发布其他对象。如果将一个 Secret 对象添加到集合 knownSecrets 中,那么同样会发布这个对象,因为任何代码都可以遍历这个集合,并获得对这个新 Secret 对象的引用。同样,如果从非私有方法中返回一个引用,那么同样会发布返回的对象。下面程序中的 UnsafeStates 发布了本应为私有的状态数组。

// 使内部的可变状态逸出(不要这么做)
class UnsafeStates {
    private String[] states = new String[] {
        "AK", "AL" ...
    };
    public String[] getStates() { return states; }
}

如果按照上述方式来发布 states,就会出现问题,因为任何调用者都能修改这个数组的内容。在这个示例中,数组 states 已经逸出了它所在的作用域,因为这个本应是私有的变量已经被发布了。

当发布一个对象时,在该对象的非私有域中引用的所有对象同样会被发布。一般来说,如果一个已经发布的对象能够通过非私有的变量引用和方法调用到达其他的对象,那么这些对象也都会被发布。

无论其他的线程会对已发布的引用执行何种操作,其实都不重要,因为误用该引用的风险始终存在。当某个对象逸出后,你必须假设有某个类或线程可能会误用该对象。这正是需要使用封装的最主要原因:封装能够使得对程序的正确性进行分析变得可能,并使得无意中破坏设计约束条件变得更难。

最后一种发布对象或其内部状态的机制就是发布一个内部的类实例,如下面程序的 ThisEscape 所示。当ThisEscape 发布 EventListener 时,也隐含地发布了 ThisEscape 实例本身,因为在这个内部类的实例中包含了对ThisEscape 实例的隐含引用。

// 隐式地使用 this 引用逸出 (不要这么做)
public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(
        	new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            }
        );
    }
}

安全的对象构造过程

在 ThisEscape 中给出了逸出的一个特殊示例,即 this 引用在构造函数中逸出。当内部的 EventListener 实例发布时,在外部封装的 ThisEscape 实例也逸出了。当且仅当对象的构造函数返回时,对象才处于可预测的和一致的状态。因此,当从对象的构造函数中发布对象时,只是发布了一个尚未构造完成的对象。即使发布对象的语句位于构造函数的最后一行也是如此。如果 this 引用在构造过程中逸出,那么这种对象就被认为是不正确构造。

不要在构造过程中使 this 引用逸出

如果想在构造函数中注册一个事件监听器或启动线程,那么可以使用一个私有的构造函数和一个公共的工厂方法(Factory Method),从而避免不正确的构造过程。

使用工厂方法来阻止 this 引用在构造过程中逸出

public class SafeListener {
    private final EventListener listener;
    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        };
    }
    
    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
}
损失函数大汇总(六十一)(Signed Distance Map Loss附公式推导与代码) 摘要 本文介绍了Signed Distance Map (SDM) Loss,一种将形状先验直接融入分割任务的方法。SDM将每个像素表示为到目标边界的带符号距离(内部为负,外部为正),相比二值mask能更连续地表达形状几何信息。SDM Loss的核心思想是让网络学习这个隐式形状场,通过可微的Heaviside函数近似将SDM转换为分割结果。文中重点分析了一种product-based regression loss,它能同时关注距离数值和内外符号的正确性,对边界区域尤为敏感。SDM Loss通常与传统的分割 阅读详情

相关推荐

COCO2017数据集类别分布深度解析

本文深入解析了COCO2017数据集的类别分布情况,包括80个常见物体的分类统计和样本数量分析。通过对比训练集与验证集的分布一致性,探讨了数据均衡性问题,并针对高频和低频类别提出了实用的数据预处理和模型训练建议,帮助优化计算机视觉任务的性能。

weixin_42533751的博客 370

【Java】VarHandle解析

一、VarHandle简介 变量句柄(VarHandle)是对于一个变量的强类型引用,或者是一组参数化定义的变量族,包括了静态字段、非静态字段、数组元素等,VarHandle支持不同访问模型下对于变量的访问,包括简单的read/write访问,volatile read/write访问,以及CAS访问。 VarHandle相比于传统的对于变量的并发操作具有巨大的优势,在JDK9引入了VarHa...

darin1997的博客 7210

大型我的世界RPG服务器开服全攻略:从环境配置到插件部署

搭建一个能支撑百人稳定在线的我的世界服务器,远不止选择服务端和安装插件那么简单。服务器架设涉及硬件选型、Java环境、端口放行、启动脚本与systemd守护等基础环节,而大型RPG玩法还依赖MythicMobs、Quests、LuckPerms等插件协同工作。理解这些技术组件的原理与配置逻辑,是确保长线运营的关键。对于希望打造原创剧情、职业副本和自定义Boss的团队而言,合理规划MySQL数据库、RCON批量管理及性能调优,能显著降低后期维护成本。本文围绕RPG服务器核心能力,从环境准备到插件部署,再到常见

weixin_27230891的博客 370

java handle_在Java 9中使用VarHandle的正确方法?

它被用于例如AtomicReference,其中以前在Java 8中使用了sun.misc.Unsafe:public final void lazySet(V newValue) {unsafe.putOrderedObject(this,valueOffset,newValue);}public final boolean compareAndSet(V expect,V update) {r...

weixin_33510623的博客 565

DAC8568控制器实战:如何用STM32实现多通道同步输出(附完整代码)

本文详细介绍了如何使用STM32微控制器驱动TI的DAC8568多通道DAC控制器,实现高精度的多通道同步模拟信号输出。内容涵盖硬件设计要点、SPI时序精准配置、驱动层命令构建,并重点解析了通过LDAC引脚实现同步更新的核心策略,提供了完整的代码框架和实战案例,帮助工程师快速解决多通道同步输出的工程难题。

weixin_29179311的博客 170

别再纠结了!手把手教你根据项目需求选对目标检测框架(MMDetection/Detectron2/YOLOv8实战对比)

本文详细对比了三大目标检测框架MMDetection、Detectron2和YOLOv8的适用场景与实战技巧,帮助开发者根据项目需求精准选型。从毕业设计到工业落地,分析不同场景下的核心需求与框架优势,提供决策树和避坑指南,确保技术选型与项目目标高度匹配。

weixin_30342209的博客 554

Java 9 变量句柄-VarHandle

Java 9发布的新特性除了最主要的模块化之外,在API方面也为开发者们带来了很多有用的特性,本篇我们来探讨一下java 9提供的新的API-VarHandle 对 memory order 的支持。在开始本篇之前,你需要对JMM(Java 内存模型)有一定的认知。 VarHandle 的必要性 随着Java中的并发和并行编程的不断扩大,我们经常会需要对某个类的字段进行原子或有序操...

泰迪的bagwell 8860

Calibre电子书阅读器个性化样式定制指南

本文详细介绍了如何通过Calibre电子书阅读器的用户样式表功能进行深度个性化样式定制。从修改全局字体、行距、背景配色(包括深色/浅色模式)到美化代码块、引用等特殊元素,本指南提供了完整的CSS代码示例与实战技巧,帮助用户打造专属的舒适阅读环境,显著提升电子书阅读体验。

vv45678的博客 920

JAVA9-Varhandle

JAVA9-Varhandle low-level-memory-opt

多头注意力探索Now 374

el-table通过上下左右键移动光标

在Element Plus的el-table中包含el-input和el-select组件,要实现上下左右键控制光标位置,需要通过focus和blur跟踪判断当前是哪个el-input或el-select有焦点,当按键按下时,通过监听,计算下一个获取焦点元素的索引,通过focus方法将焦点切换至改元素。

秋思的博客 1350

Android Studio安装常见问题详解——Android Emulator Hypervisor Driver for AMD Processors installation failed.

目录一、问题描述二、解决方案2.1 开启CPU虚拟化2.2 禁用Hyper-V2.3 使用管理员命令行在驱动程序包中执行“silent_install.bat” 一、问题描述 新买的AMD轻薄本,在安装Android Studio时遇到问题: Android Emulator Hypervisor Driver for AMD Processors installation failed. 笔记本配置为: 二、解决方案 2.1 开启CPU虚拟化 设置虚拟化的步骤这里不进行详细介绍了。如果需要,可以自行百

Wendy的博客 4万+

Java并发编程VarHandle类

VarHandle是一种强大且灵活的工具,适用于需要高性能并发控制和变量操作的场景。它提供了一种更高效、更灵活的方式来操作变量,同时确保线程安全。通过 VarHandle,开发者可以更精细地控制变量的访问和更新,从而提升并发程序的性能和可靠性。

Marblog 666

TGAT Python:图神经网络中的动态图算法

其中,图神经网络中的动态图算法成为了研究的热点之一。在模型的 forward 方法中,通过循环调用 TGATLayer 实例来进行图神经网络的前向传播。最终,模型返回图中节点的表示向量 output,可以用于图分类等任务。TGAT 算法的核心思想是在节点之间建立注意力连接,以捕捉节点在不同时间步之间的关系。TGAT 算法通过引入注意力机制和时间信息,从而有效地对动态图数据进行建模和分析。通过以上 Python 代码,我们可以方便地构建和训练 TGAT 模型,并在图分类任务中应用它。

2301_79366435的博客 1828

Java并发编程-对象共享

本文将介绍Java并发编程中的对象共享问题和解决办法。 java并发访问共享数据的三种方式: 1、synchronized 对共享变量进行变更的方法、代码块 使用synchronized关键字(或者Lock)。 2、对共享变量使用volatile关键字。 3、使用Atomic包中的原子性操作类。 ...

藏经阁 764

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

java并发编程基础之对象的共享

java并发编程基础之对象的共享标签 : 多线程java并发编程基础之对象的共享 1可见性 11失效数据 12 非原子的64位操作 13加锁与可见性 14 Volatile变量 2发布和逸出 3 线程封闭 31 Ad-hoc线程封闭 32 栈封闭 33 ThreadLocal类 4 不变性 41 Final域 3.1可见性可见性指的是多线程中,一个线程对共享数据的修改是对其他线程是否可见的一种性质。

w574240966的专栏 611

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

《java并发编程实战》 第三章 对象如何共享

《java并发编程实战笔记》第三章 对象的共享可见性失效数据最低安全性volatile变量竞态条件可用原子变量类简单修复计算器的竞态条件内置锁重入用锁来保护共享状态如何保证线程活跃性与安全性 第三章 对象的共享 从第三章开始,(这本书就开始变态起来了),如何共享和发布对象,从而使它们能安全的被多个线程同时访问。 可见性 同步代码块和同步方法可以确保以原子方式执行操作,一种常见的误解,认为关键字s...

乌鸦大大 1057

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

【Java并发编程系列5】对象的共享

主要总结《Java并发编程实战》中“第3章:对象共享”的内容。 前言 在没有Java相关并发知识的前提下,第一次看这本书《Java并发编程实战》,其实有些看不太懂,因为里面的很多知识讲的比较抽象,比如可见性、volatile、final等讲的其实都不深入,所以导致自己理解的也很片面。后来就先专门看了“Java内存模型”相关的知识,再对相关知识理解起来,就要深入一些,所以才有了前面写的4篇关于“Java内存模型”相关的文章。 “第二章:基础知识”主要讲解线程安全性、原子性、加锁机制(主要讲解内置...

飞楼踏梦 171

Java并发编程系列5 - 对象的共享

【Java并发编程系列5】对象的共享

开心博客 349

【java并发编程】线程安全性和对象共享

读书笔记来自《java并发编程实战》。 线程安全性 可重入锁 可重入意味着获取锁的操作的粒度是线程而不是调用。 实现方法是为每个锁关联一个获取计数值和一个所有者进程。当计数器为0,这个锁被认为可以被任何线程池游。当线程请求一个未被池游的锁时,JVM将记下锁的持有者,并且将计数器置为1,如果同一个线程再次获取这个锁,计数器将递增。当线程退出痛不快,计数器递减。当计数器为0,锁被释放。 ...

Zoom 744

Java并发编程实战之 线程安全性、对象的共享、对象的组合

线程安全性 当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误。有三种方式修复这个问题: 不在线程之间共享该状态变量 将状态变量修改为不可变类型 在访问状态变量时使用同步 内置锁 Java 提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。 synchroized (lock) { // 访问或修改由锁保护的共享状态 } 每个 Java 对象都可以用做一

weixin_44593818的博客 846

Java并发编程实战 第3章 对象的共享

3.1 可见性 在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。 3.1.1 失效数据 3.1.2 非原子的64位操作 最低安全性:即使是失效值,但至少是之前的值,不是随机值。 例外:非volatile类型的64位数值变量(long、double)。 Java内存模型要求:变量的读取或写入都是原子操作,但是对非volatile的long和double变量,JVM允许将64位的读操作或写操作分解为两个32位的操作。 当读取一个非volatile的long变量时,如果

可可西里 682

java并发编程(三)对象共享

可见性 很多人认为synchronized只能用于实现原子性或者确定临界区,忽视了它另一个重要的作用,确保内存可见性。 每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。所以在写线程没有将工作内存的数据同步到主存前其他线程是无法看先写入的值的。 指令重排序就是在不影响程序逻辑结果时为了充分利用cpu指令的执行顺...

小生的博客 375

《Java并发编程实战》笔记3:对象的共享

1 什么是可见性,再说说volatile变量 JVM内存模型时总结了 2 什么是对象的发布与逸出? 发布(Publish):一个对象是指,使对象能够在当前作用域之外的代码中使用。 逸出(Escape):是一种错误的发布, 当一个对象还没有构造完成时 , 就使得它被其他线程看见。 3 发布对象的几种情形 (1) 将对象的引用保存到一个公有的静态变量中 (2) 如果一个已经发布的对象能够通过非私有的变...

sixabs的博客 206

《Java并发编程实战》第三章 对象的共享 读书笔记

一、可见性 什么是可见性? Java线程安全需要防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且需要确保当一个线程修改了对象的状态后,其他线程可以看到发生的状态变化。 后者就是可见性的描述即多线程可以实时获取其他线程修改后的状态。      *** 待补充   两个工人同时记录生产产品总数问题 1. 失效数据 可见性出现问题就是其他线程没有获取到修改后的状态,更直观

Android 1875

java 对象共享_【Java并发编程实战】03对象的共享

上一篇介绍了如何通过同步多个线程避免同一时刻访问相同数据,本篇介绍如何共享和发布对象,使它们被安全地由多个进程访问。1.可见性通常,我们无法保证执行读操作的线程能看到其他线程写入的值,因为每个线程都由自己的缓存机制。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。public class NoVisibility {private static boolean ready;priva...

weixin_29215391的博客 214

JAVA并发编程之对象的共享

目录 带(数字)为术语编号—— 术语地址 这一章看的东西乍看之下感觉都是显而易见但是深究起来又别有洞天,总能有所得的。这章主要讲的是并发编程下对象共享时会出现的一些问题,而我主要记录一下我觉得有所得内容或有启发的例子。 1.(内存)可见性 内存可见性 单单从字面上来理解可能会有一些歧义,状态1 就在那里,有什么可见不可见,能访问不就是可见么?但是这里的可见性不是指这个意思。我个人理解为...

LZ的博客 954
上一篇: Java并发编程 线程安全性
下一篇: java.lang.IllegalStateException 错误解决过程
liam12138
博客等级 码龄8年 1粉丝 17原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值