并发编程2:如何进行对象共享?

news2024/11/26 15:33:59

目录

1、对象的可见性:Volatile 变量

2、发布和逸出

3、线程封闭:ThreadLocal

4、对象的不变性

5、安全发布

5.1 - 安全发布常用的模式

5.2 - 可变对象

5.3 - 安全地共享对象


        同步在用于实现原子性或者确定 “临界区(Critical Section)” 的同时,还有另一个重要的作用:内存可见性 (Memory Visibility)//一个线程对共享变量的修改操作对另一个线程可见

1、对象的可见性:Volatile 变量

        首先,贴一段代码:

public class NoVisibility {

    private static boolean ready;
    private static int     number;

    private static class ReaderThread extends Thread {

        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)”//由于重排序的存在,使线程操作没有按照程序中定义的顺序来执行

       内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果,如下图所示。

        当线程 B 执行由锁保护的同步代码块时,可以看到线程 A 之前在同一个同步代码块中的所有操作结果。

        现在,我们可以进一步理解为什么在访问某个共享且可变的变量要求所有线程在同一个锁上同步,就是为了确保某个线程写入该变量的值对于其他线程来说都是可见的。否则,如果一个线程在未持有正确锁的情况下读取某个变量,那么读到的可能是一个失效值//同一个锁,保证可见性

        加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。

        Volatile 变量

        Java 语言提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。//轻量级同步,但是并不能保证互斥(原子操作,即读写不分离)

        当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的因此不会将该变量上的操作与其他内存操作一起重排序。volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile 类型的变量时总会返回最新写入的值。//禁止重排序和缓存

        volatile 变量通常用做某个操作完成、发生中断或者状态的标志//比如终结循环标志

        虽然 volatile 变量很方便,但也存在一些局限性,volatile 的语义不足以确保操作的原子性//多个线程对同一个 volatile 变量进行修改操作,仍然会出现数据安全问题

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

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

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

2、发布和逸出

        “发布(Publish)”一个对象的思是指,使对象能够在当前作用域之外的代码中使用。例如,将一个指向该对象的引用保存到其他代码可以访问的地方,或者将引用传递到其他类的方法中。

        发布内部状态可能会破坏封装性,并使得程序难以维持不变性条件。例如,如果在对象构造完成之前就发布该对象,就会破坏线程安全性。当某个不应该发布的对象被发布时,这种情况就被称为逸出( Escape)

        发布对象的最简单方法是将对象的引用保存到一个公有的静态变量中,以便任何类和线程都能看见该对象。如下所示:

    //保存引用
    public static Set<Secret> knownSecrets;

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

        当发布某个对象时,可能会间接地发布其他对象。如果将一个 Secret 对象添加到集合knownSecrets 中,那么同样会发布这个对象,因为任何代码都可以遍历这个集合,并获得对这个新 Secret 对象的引用。

        不要在构造过程中使 this 引用逸出//对象逸出会存在线程安全问题

//ThisEscape对象逸出
public class ThisEscape {

    public ThisEscape(EventSource source) {
        //不要再构造函数中注册一个监听器或者启动一个线程
        source.registerListener(new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        });
    }
}

        当 ThisEscape 发布 EventListener 时,也隐含地发布了 ThisEscape 实例本身,因为在这个内部类的实例中包含了对 ThisEscape 实例的隐含引用,如果 this 引用在构造过程中逸出,那么这种对象就被认为是不正确构造//内部类隐式包含了外部内的引用,对象可能还没创建完成就被使用

      在构造过程中使 this 引用逸出的一个常见错误是,在构造函数中启动一个线程。当对象在其构造函数中创建一个线程时,无论是显式创建(通过将它传给构造函数)还是隐式创建(由于 Thread 或 Runnable 是该对象的一个内部类) this 引用都会被新创建的线程共享。在对象尚未完全构造之前,新的线程就可以看见它。在构造函数中创建线程并没有错误,但最好不要立即启动它,而是通过一个 start 或 initialize 方法来启动。

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

public class SafeListener {

    private final EventListener listener;
    //1-私有构造
    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        };
    }
    //2-公共方法:防止this逸出
    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
}

3、线程封闭:ThreadLocal

        当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭 (ThreadConfinement),它是实现线程安全性的最简单方式之一。//即不进行数据共享

        栈封闭线程封闭的一种特例,在栈封闭中,只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。//使用局部变量

        ThreadLocal 对象通常用于防止对可变的单实例变量 (Singleton)全局变量进行共享。例如,在单线程应用程序中可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法时都要传递一个 Connection 对象。通过将 JDBC 的连接保存到 ThreadLocal 对象中,每个线程都会拥有属于自己的连接。//封装数据源是ThreadLocal的经典应用

4、对象的不变性

        满足同步需求的另一种方法是使用不可变对象 (Immutable Object)

        如果某个对象在被创建后其状态就不能被修改,那么这个对象就称为不可变对象。不可变对象一定是线程安全的

@Immutable
public final class ThreeStooges {
    //一旦构造完成就不能修改
    private final Set<String> stooges = new HashSet<String>();

    //构造函数
    public ThreeStooges() {
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
    }

    public boolean isStooge(String name) {
        return stooges.contains(name);
    }
}

        在不可变对象的内部仍可以使用可变对象来管理它们的状态,如上边代码 ThreeStooges 所示。尽管保存姓名的 Set 对象是可变的,但在 Set 对象构造完成后无法对其进行修改。//不可变对象在构造完成后就不能进行修改

5、安全发布

5.1 - 安全发布常用的模式

        可变对象必须通过安全的方式来发布,这通常意味着在发布和使用该对象的线程时都必须使用同步。

        要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:

  • 在静态初始化函数中初始化一个对象引用。//使用static关键字
  • 将对象的引用保存到 volatile 类型的成员变量或者 AtomicReferance 对象中。//保证可见性
  • 将对象的引用保存到某个正确构造对象的 final 类型域中//依赖注入用的就是 final 域
  • 将对象的引用保存到一个由锁保护的域中。//使用同步容器等

        如果线程 A 将对象 X 放入一个线程安全的容器,随后线程 B 读取这个对象,那么可以确保 B 看到 A 设置的 X 状态,即便在这段读/写 X 的应用程序代码中没有包含显式的同步。在 Java 线程安全库中的容器类提供了以下的安全发布保证://使用安全容器进行发布

  • 通过将一个键或者值放入 Hashtable、synchronizedMap 或者 ConcurrentMap 中,可以安全地将它发布给任何从这些容器中访问它的线程。
  • 通过将某个元素放入 Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList 或 synchronizedSet 中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程。
  • 通过将某个元素放人 BlockingQueue 或者 ConcurrentLinkedQueue 中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程。

        通常,要发布一个静态构造的对象,最简单和最安全的方式是使用静态的初始化器

public static Holder holder = new Holder(42);

        静态初始化器由 JVM 在类的初始化阶段执行。由于在 JVM 内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布。

5.2 - 可变对象

        如果对象在构造后可以修改,那么安全发布只能确保“发布当时”状态的可见性。对于可变对象,不仅在发布对象时需要使用同步,而且在每次访向对象时同样需要使用同步来确保后续修改操作的可见性。要安全地共享可变对象,这些对象就必须被安全地发布,该对象必须是线程安全的或者由某个锁保护起来。

        对象的发布需求取决于它的可变性:

  • 不可变对象可以通过任意机制来发布。
  • 事实不可变对象必须通过安全方式来发布。//事实不可变:对象发布后不再被修改
  • 可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。

5.3 - 安全地共享对象

        当获得对象的一个引用时,你需要知道在这个引用上可以执行哪些操作。在使用它之前是否需要获得一个锁?是否可以修改它的状态,或者只能读取它?许多并发错误都是于没有理解共享对象的这些“既定规则”而导致的。当发布一个对象时,必须明确地说明对象的访问方式//当对象发布时,就需要考虑它的同步问题

        在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:

        线程封闭。线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。//对于成员变量的定义需要格外小心

        只读共享。在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象

        线程安全共享。线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。//同步容器的做法

        保护对象被保护的对象只能通过持有特定的锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。//并发编程常使用的方式

        总结:如果需要安全的共享对象,那么需要安全的发布对象。安全的发布对象,需要保证对象的完整性以及可见性。

        //勉励:没有什么知识是一次性学会的,学习知识的过程就是对思想一次次的打磨过程

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

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

相关文章

大公司搞网络部署,关键在哪步?

下午好&#xff0c;我是老杨。 很久没有聊网络部署&#xff0c;今天就说这事儿。 要进行网络部署&#xff0c;涉及的技术点/知识有哪些&#xff1f; 最基本的&#xff0c;你需要掌握vlan划分、路由选择、出口nat处理&#xff1b;你可能还需要懂得链路聚合、mstp、vrrp、ospf…

恒运资本:沪指震荡微涨,医药、酿酒板块反弹,传媒板块活跃

8日早盘&#xff0c;沪指早盘弱势震动下探&#xff0c;临近午盘翻红&#xff1b;深成指、创业板指均止跌回升&#xff1b;两市半日成交超5000亿元&#xff0c;北向资金净卖出超40亿元。 截至午间收盘&#xff0c;沪指微涨0.01%报3269.29点&#xff0c;深成指跌0.06%&#xff0c…

案例分享:NetApp SSD 硬盘重启后全部故障

近日连续处理了几个NetApp FAS存储系统SSD磁盘重启后&#xff0c;全部故障的案例。这里是case的总结和分享&#xff0c;以后有遇到的可以参考处理。 案例1&#xff1a;客户一套FAS8020&#xff0c;带一个DS2246盘柜&#xff0c;内置24个800G X447A的磁盘&#xff0c;机房掉电后…

2023网络安全常用工具汇总(附学习资料+工具安装包)

几十年来&#xff0c;攻击方、白帽和安全从业者的工具不断演进&#xff0c;成为网络安全长河中最具技术特色的灯塔&#xff0c;并在一定程度上左右着网络安全产业发展和演进的方向&#xff0c;成为不可或缺的关键要素之一。 话不多说&#xff0c;网络安全10款常用工具如下 1、…

SQL力扣练习(十)

目录 1.体育馆的人流量(501) 示例 1 解法一&#xff08;row_number&#xff08;&#xff09;&#xff09; 解法二&#xff08;自定义变量&#xff09; 解法三 2.好友申请&#xff08;602&#xff09; 示例 解法一&#xff08;union all&#xff09; 解法二 3.销售员&…

hive修改表或者删除表时卡死问题的解决(2023-08-08)

背景&#xff1a;前阶段在做hive表的改表名时&#xff0c;总是超时&#xff0c;表是内部表&#xff0c;数据量特别大&#xff0c;无论你是修改表名还是删除表都是卡死的状态&#xff0c;怎么破&#xff1f; 终于&#xff1a;尝试出来一个新的方法 将内部表转化成外部表&#…

面试常问:tcp的三次握手和四次挥手你了解吗?

三次握手和四次挥手是各个公司常见的考点&#xff0c;一个简单的问题&#xff0c;却能看出面试者对网络协议的掌握程度&#xff0c;对问题分析与解决能力&#xff0c;以及数据流管理理解和异常情况应对能力。所以回答好一个tcp的三次握手和四次挥手的问题对于我们的面试成功与否…

【雕爷学编程】Arduino动手做(193)---移远 BC20 NB+GNSS模块13

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

保障容器应用安全的6个建议

虽然容器技术出现已经超过了十年时间&#xff0c;但由于其应用的轻量性、快捷性和灵活性&#xff0c;使得容器应用的流行程度一直保持了快速的增长趋势&#xff0c;并逐渐成为云原生环境中部署业务应用和工作负载的不二选择。在容器应用快速普及的发展背景下&#xff0c;要确保…

塔里木水系分布图

声明&#xff1a;来源网络&#xff0c;仅供学习&#xff01;

Netty自定义编码解码器

上次通信的时候用的是自带的编解码器&#xff0c;今天自己实现一下自定义的。 1、自定义一下协议 //协议类 Data public class Protocol<T> implements Serializable {private Long id System.currentTimeMillis();private short msgType;// 假设1为请求 2为响应privat…

小兔鲜项目 uniapp (1)

目录 项目架构 uni-app小兔鲜儿电商项目架构 小兔鲜儿电商课程安排 创建uni-app项目 1.通过HBuilderX创建 2.通过命令行创建 pages.json和tabBar案例 uni-app和原生小程序开发区别 用VS Code开发uni-app项目 拉取小兔鲜儿项目模板代码 基础架构–引入uni-ui组件库 操…

大模型:突破AI的边界

引言 人工智能&#xff08;AI&#xff09;在过去几年中取得了巨大的进展&#xff0c;其中大模型被认为是取得这些进展的关键因素之一。大模型具有更多的参数、更强的表达能力和更高的预测性能&#xff0c;对自然语言处理、计算机视觉和强化学习等任务产生了深远的影响。本文将探…

赛码网-Light 100%AC代码(C++)

———————————————————————————————————— ⏩ 大家好哇&#xff01;我是小光&#xff0c;嵌入式爱好者&#xff0c;一个想要成为系统架构师的大三学生。 ⏩最近在准备秋招&#xff0c;一直在练习编程。 ⏩本篇文章对赛码网的 Light 题目做一个…

pocky-request网络请求插件

插件下载地址&#xff1a;https://ext.dcloud.net.cn/plugin?id468 插件&#xff1a;https://www.yuque.com/pocky/aaeyux/irx7u0#Oosbz 使用教程&#xff1a; 下载插件main.js中配置&#xff1a; // 导入 import axiosRequest from ./js_sdk/pocky-request/pocky-request…

Vben框架使用小记

渲染表格可展开内容&#xff1a; <!-- 这里是一个具名插槽&#xff0c;渲染可展开的内容模板 --><template #expandedRowRender"{ record }">效果图&#xff1a;

企业举办活动邀请媒体的意义和重要性

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 企业举办活动并邀请媒体的意义和重要性是多方面的&#xff0c;主要有以下一些&#xff1a; 1. 品牌曝光与宣传&#xff1a;邀请媒体参与企业活动可以提高企业的品牌曝光度。媒体报道能够…

PHP实现保质期计算器

1.php实现保质期计算&#xff0c; 保质期日期可选&#xff0c;天 、月、年 2. laravel示例 /*** 保质期计算器* return void*/public function expirationDateCal(){$produce_date $this->request(produce_date); // 生产日期$warranty_date $this->reques…

2023最新性能测试面试题(带答案)

一、性能测试开展过程&#xff1a; 答&#xff1a;第一步&#xff1a;找产品沟通哪些接口需要压测&#xff0c;需要达到什么样的预期值(TPS和响应时间) 第二步&#xff1a;编写测试计划&#xff0c;人员、时间周期、工具 第三步&#xff1a;环境搭建 第四步&#xff1a;造数…

现在pmp还值得去考试吗?

一&#xff0c;为什么要考PMP&#xff1f; 1. PMP认证在项目管理领域具有极高的认可度&#xff0c;是全球通用的认证&#xff0c;不仅局限于某一行业或地区。目前已有超过43万人参加了PMP考试。 2. PMP持证者的薪资和收入潜力都有明显优势。根据PMI发布的《薪酬力&#xff1a…