Java并发编程实践学习笔记(三)——共享对象之可见性

news2024/12/28 21:28:04

目录

1 过期数据        

2 非原子的64位操作

3 锁和可见性

4 Volatile变量(Volatile Variables)


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

        下面是一个可见性的例子,在没有同步的情况下共享变量(不要这么做):

public class NoVisibility {

    private static boolean ready = false;
    private static int number;

    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready) {
                // 线程让步, 暂停当前正在执行的线程对象,并执行自己或其他线程。
                Thread.yield();
                System.out.println(Thread.currentThread().getName() + ": " + number);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new ReaderThread().start();
        number = 42;
        ready = true;
        System.out.println(Thread.currentThread().getName() + ": " + number);
    }
}

        运行是没问题的,正常输出42,但不代表这个程序是OK的,在代码中,主线程和读线程都将访问共享变量 ready 和 number。虽然 NoVisibility 看起来会输出 42,但事实上,可能会发生以下两种情况:

(1)Novisibility可能会持续循环,因为ReaderThread可能会看不到写入ready的值。

(2)NoVisibility可能会输出0,因为ReaderThread可能会看到写入ready的值,却没有看到写入number的值。这种现象称为”重排序(Reordering)",在没有同步的情况下,编译器、 处理器、运行时都可能对操作的执行顺序进行调整)..

       只要在某个线程中无法检测到重排序情况,那么就无法确保线程中的操作将按程序中指定的顺序执行。当主线程首先写入 number ,然后再没有同步的情况下写入 ready, 那么读线程看到的顺序可能与写入的顺序完全相反。

        换一个容易复现的例子:

public class VisibilityTest {


    boolean isStop = false;

    public void test(){
        Thread t1 = new Thread(){
            public void run() {
                isStop=true;
            }
        };
        Thread t2 =  new Thread(){
            public void run() {
                while (!isStop);
            }
        };

        t2.start();
        t1.start();

    }
    public static void main(String args[]) throws InterruptedException {
        // 为了方便复现,设置了多次循环
        for (int i = 0; i <30; i++){
            new VisibilityTest().test();
        }
    }
}

        这段代码可能永远不会结束,因为线程t1对isStop的赋值,线程t2可能对此并不可见。解决办法就是把共享变量添加volatile 关键字。这个例子中,这样改动就可以正常退出了:

volatile boolean isStop = false;

1 过期数据        

        Novisibility 展示了缺乏同步可能得到一个已经失效的值:失效数据。当读线程查看ready变量时,可能会得到一个已经失效的值。除非在每次访问变量时都是用同步,否则很可能获得该变量的一个失效值。更糟糕的是,失效值可能不会同时出现:一个线程可能获取到某个变量的最新值,却获得另一个变量的失效值。有时候要确保可见性,仅仅对 set 方法进行同步是不够的,需要对 get 和 set 方法都需要进行同步。

         下面的例子MutableInteger不是线程安全的,因为get和set都是在没有同步的情况下访问value的。失效值问题容易出现:如果某个线程调用了set,那么里一个正在调用get的线程可能会看到更新后的value,也可能看不到。

public class MutableInteger {
    private int value;
 
    public int get() {
        return value;
    }
 
    public void set(int value) {
        this.value = value;
    }
}

       要解决这个问题,需要对set和get用synchronized关键字修饰进行同步。仅对set方法进行同步是不够的,调用get的线程仍然会看到失效值。 

public class SynchronizedInteger {

	private int value;

	public synchronized int get() {
		return value;
	}

	public synchronized void set(int value) {
		this.value = value;
	}
}

2 非原子的64位操作

        当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是一个随机值。这种安全性保证也被成为最低安全性(out-of-thin-air satety)

        最低安全性适用于绝大多数变量,但存在例外:非volatile(不稳定的,易变的)类型的64位数值变量(double和long),java内存模型要求,变量的读取曹组和写入操作都必须是原子操作,但对于非volatile类型的long和double变量,JVM允许将64位的读操作或写操作分解为两个32位的操作。当读取一个非volatile的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么可能会读取到某个值的高32位和另一个值的低32位。因此,即使不考虑失效数据的问题,在多线程程序中使用共享且可变的long和double等类型的变量也是不安全的,除非用关键字volatile来声明它们,或者用锁保护起来

       虽然 JVM 规范并没有要求64位变量的读写为原子操作,但是现在基本上所有的商业虚拟机都将其实现为原子操作。

3 锁和可见性

        内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果。如下图,当线程A执行某个同步代码块时,线程B随后进入由同一个锁保护的同步代码块,在这种情况下可以保证,在锁释放之前,A看到的变量值在B获得锁后同样可以由B看到。即当B执行由锁保护的同步代码块时,可以看到A之前在同一个同步代码块中的所有操作。如果没有同步,那么将无法实现上述保证。

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

4 Volatile变量(Volatile Variables)

       Java 提供了一种弱同步机制 volatile ,可以用它来保证状态的可见性和有序性。当把变量声明为 volatile 之后,虚拟机在运行当前指令的时候,会建立一个内存屏障(Memory Barrier 或 Memory Fence),阻止重排序时将后面的指令重排序到内存屏障之前的位置。所以,读一个volatile 类型的变量时,总会返回由某一线程所写的最新值。volatile 变量是一种比 synchronized 关键字更轻量级的同步机制。

       虽然 volatile 变量使用十分方便,但也存在着一定的局限性。它通常用来做某个操作完成、发生中断或者状态的标志。 虽然 volatile 变量也可以用于表示其他的状态信息,但使用时要非常小心。例如, volatile 的语义不足以保证递增(count++)操作的原子性。

       下面例子给出了volatile变量的一种典型用法:检查某个状态标记以判断是否退出循环。这个例子中线程通过类似数绵羊的方法进入休眠状态。asleep必须用volatile修饰。否则,当asleep被另一个线程修改时,执行判断的线程却发现不了。这里也可以用锁来确保asleep更新操作的可见性,但这将使代码复杂。

// 数绵羊
volatile boolean asleep;
...
    while (!asleep)
        countSomeSheep();

       加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。volatile无法保证对变量的任何操作都是原子性的.

       使用volatile的情况:

     (1)对变量的写入操作不依赖变量的当前值,或确保只有单个线程更新变量的值(如果多个线程都依赖于原值,那么当变量发生非原子操作时,多个线程读取到的变量就不能保证一致了);

     (2)该变量不会与其他状态变量一起纳入不变性条件中;

     (2)在访问变量时不需要加锁。

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

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

相关文章

java状态机实现订单状态转移

一、状态机 状态机是状态模式的一种应用&#xff0c;相当于上下文角色的一个升级版。在工作流或游戏等各种系统中有大量使用&#xff0c;如各种工作流引擎&#xff0c;它几乎是状态机的子集和实现&#xff0c;封装状态的变化规则。状态机可以帮助开发者简化状态控制的开发过程…

APP界面设计都有哪些好用的软件推荐

基于APP界面的不同功能&#xff0c;所选择的APP界面设计软件也会有所不同。然而&#xff0c;并不是说所有的APP界面设计软件都非常精通&#xff0c;熟练地学习几个常用的APP界面设计软件。以下10个APP界面设计软件将为您的团队提供绘制APP界面所需的必要功能。 1.即时设计 即…

OpenCV-Python实战(7) —— OpenCV 实现抖音视频倒放效果

1. 需求分析 参考&#xff1a;十行Python代码制作一个视频倒放神器&#xff0c;由于最近在学习 OpenCV &#xff0c;因此试着使用 OpenCV 进行实现&#xff0c;学以致用&#xff08;胡乱折腾&#xff09;。 需要视频倒放&#xff0c;因此需要读取视频cv.VideoCapture&#xff1…

一键docker搭建mysql主从环境

一键docker搭建mysql主从环境 初衷准备阶段操作阶段注意事项 初衷 一开始为了玩一下shared-jdbc&#xff0c;要搭Mysql主从环境&#xff0c;这玩意虽然搭好&#xff0c;之后使用要是网络问题&#xff0c;或者sql执行出错&#xff0c;还得重新调Binlog位置&#xff0c;麻烦得很…

接口自动化测试之HTTP协议详解(敢称全网最全)

目录 协议 OSI模型 HTTP URL 报文 响应报文 HTTP扩展 协议 简单理解&#xff0c;计算机与计算机之间的通讯语言就叫做协议&#xff0c;不同的计算机之间只有使用相同的协议才能通信。所以网络协议就是为计算机网络中进行数据交换而建立的规则&#xff0c;标准或约定的集…

Node.js 使用RSA加密/解密

在本文中&#xff0c;我们将探讨如何在 Node.js 中使用 RSA 加密和解密。RSA 是一种非对称加密算法&#xff0c;它可以确保数据的安全传输。使用 RSA&#xff0c;我们可以在不直接传输密钥的情况下安全地加密和解密数据。 一、安装依赖 我们将使用 node-rsa 库来执行加密和解密…

2023 年Java经典面试题,基础篇01(持续更新)

本篇文章主要讲的是 2023 年Java最新面试题&#xff0c;持续更重中 基础概念与常识 原文地址&#xff1a;https://github.com/Snailclimb/JavaGuide Java 语言有哪些特点? 简单易学&#xff1b;面向对象&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#…

《LeetCode》—— LeetCode刷题日记

本期&#xff0c;我给大家讲述的是关于 n数之和这类题目的讲解&#xff0c;我会给大家讲解两数之和&#xff0c;三数之和和四数之和这三道题目。 目录 &#xff08;一&#xff09;两数之和 &#xff08;二&#xff09;三数之和 &#xff08;三&#xff09;四数之和 &#xf…

NodeJs 最近各版本特性汇总

&#xff08;预测未来最好的方法就是把它创造出来——尼葛洛庞帝&#xff09; NodeJs 官方链接 github链接 V8链接 Node.js发布于2009年5月&#xff0c;由Ryan Dahl开发&#xff0c;是一个基于Chrome V8引擎的JavaScript运行环境&#xff0c;使用了一个事件驱动、非阻塞式I/O模…

对象应用:C++字符串和vector,对象的new与delete重构

对象应用 C字符串和vector字符串创建方式字符串拼接字符串追加 字符串截断autovector创建方式vector操作 new与delete重构new与delete的工作步骤new与delete重构应用只能生成栈对象只能生成堆对象 C字符串和vector C的字符串是一个对象&#xff0c;存在于std标准库中&#xff0…

Python基础入门(4)—— 什么是偷懒编程法?是类、对象和继承

文章目录 00 | &#x1f603;为什么学习类&#xff1f;&#x1f603;01 | &#x1f604;创建类&#x1f604;02 | &#x1f606;创建对象&#x1f606;03 | &#x1f609;访问对象属性和方法&#x1f609;04 | &#x1f60a;构造函数&#x1f60a;05 | &#x1f60b;继承&#…

Shell编程之数组

目录 一、数组的基本概念 二、定义数组的方法 方法一&#xff1a; ​编辑 方法二&#xff1a; 方法三&#xff1a; ​编辑 方法四&#xff1a; 三、 数组的输出&#xff0c;删除和长度统计 1&#xff09;数组元素的输出 2&#xff09;数组全部元素输出 3&#xff0…

一种用于提高无线传感器网络寿命的改进LEACH协议(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 无线传感器网络具有网络灵活性强、网络规模可变等优点&#xff0c;广泛应用于军事、工业等领域。无线传感器网络的基本网络路由…

Mybatis一级缓存详解

目录 一级缓存 一级缓存的组织 一级缓存的生命周期 一级缓存的工作流程 Cache接口的设计以及CacheKey的定义 一级缓存的性能分析 一级缓存与Spring 事务一级缓存存在的弊端 官方文档分析 Spring通过Mybatis调用数据库的过程 一级缓存 对于会话&#xff08;Session&am…

Nacos-01-Nacos基本介绍

背景 ​ 服务发现是⼀个古老的话题&#xff0c;当应用开始脱离单机运行和访问时&#xff0c;服务发现就诞生了。目前的网络架构是每个主机都有⼀个独立的 IP 地址&#xff0c;那么服务发现基本上都是通过某种方式获取到服务所部署的 IP 地址。DNS 协议是最早将⼀个网络名称翻译…

让AI帮忙写个需求,AI写出来了,只是有bug而已

需求 使用原生JS和iframe&#xff0c;嵌入网页进行轮播&#xff0c;需要可以点击暂停、上一页、下一页。 AI的答案 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>轮播图</title><style>* {margin: 0;padd…

看板项目管理:如何可视化工作以提高生产力?

如果你一直关心优化工作流程&#xff0c;提高你或团队的生产力&#xff0c;你肯定听说过看板这个词。 看板是一种工作管理方法&#xff0c;可以将整个工作流程以及构成工作流程的每个单独活动可视化&#xff0c;从而可以识别瓶颈和优化整体流程。 在这方面&#xff0c;看板的…

Python基础(二)

目录 一、类型转换 1、为什么需要数据类型转换 2、数据类型转化的函数 3、str()函数类型转换使用 4、int()函数类型转换使用 4.1int()不能将str类型数据转换成int 4.2int()将bool类型转换成int 4.3int()将float转换成int 5、Float()函数类型转换使用 5.1Float()函数不…

ros imu可视化(ubantu)

可以用下面的链接安装ros 安装ros 在home下建立workspace&#xff0c;添加环境变量 export $ROS_PACKAGE_PATHROS_PACKAGE_PATH:/home/workspace在workspace下建立src文件夹&#xff0c;将fdilink_ahrs_ROS1解压在src目录下面 在workspace下运行以下命令&#xff1a; catkin_…

【牛客面试必刷TOP101】有效括号序列、滑动窗口的最大值

BM44 有效括号序列 点击进入该题 描述&#xff1a; 给出一个仅包含字符’(‘,’)‘,’{‘,’}‘,’[‘和’],的字符串&#xff0c;判断给出的字符串是否是合法的括号序列。 括号必须以正确的顺序关闭&#xff0c;"()“和”()[]{}“都是合法的括号序列&#xff0c;但”(]“…