并发编程-并发三大特性

news2024/11/13 3:37:13

并发三大特性

并发编程Bug源头:原子性、可见性和有序性问题。

原子性

一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。

注意:不采取任何的原子性保障措施的自增操作并不是原子性的,比如i++操作。

原子性案例:

// 模拟多线程累加操作
public class AtomicTest
{
    private static volatile int counter = 0;
    
    public static void main(String[] args)
    {
        for (int i = 0; i < 10; i++)
        {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 10000; j++)
                {
                    counter++;
                }
                
            });
            thread.start();
        }
        
        try
        {
            Thread.sleep(3000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        // 思考counter=?
        System.out.println(counter);
    }
}

未保证原子性,输出结果:
38865(随机值)
如何保证原子性
  • 通过 synchronized 关键字保证原子性

  • 通过 Lock锁保证原子性

  • 通过 CAS保证原子性

思考:在 32 位的机器上对 long 型变量进行加减操作是否存在并发隐患?

存在安全隐患,32位机器上操作 long 类型分为两步:高32位、低32位,并发可能有脏读问题

可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

可见性案例:

// 模拟两个线程对共享变量操作
public class VisibilityTest
{
    // volatile -> lock addl $0x0,(%rsp)
    // 1、volatile 保证可见性
    private volatile boolean flag = true;
    
    // private volatile int count;
    
    public synchronized void refresh()
    {
        // 希望结束数据加载工作
        flag = false;
        System.out.println(Thread.currentThread().getName() + "修改flag:" + flag);
    }
    
    public void load()
    {
        System.out.println(Thread.currentThread().getName() + "开始执行.....");
        while (flag)
        {
            // TODO 业务逻辑:加载数据
            
            // 2、让出CPU时保证可见性,1000不会跳出循环,10000跳出,因为时间片用完了
            // shortWait(10000);
            
            // 3、synchronized 保证可见性
            // System.out.println("正在加载数据......");
            
            // 4、volatile count 保证可见性
            // count++;
            
            // 5、添加一个内存屏障,保证可见性
            // UnsafeFactory.getUnsafe().storeFence();
            
            // 6、sleep 保证可见性,方法内部使用了内存屏障
            // try {
            // Thread.sleep(0);
            // } catch (InterruptedException e) {
            // throw new RuntimeException(e);
            // }
            
            // 7、保证可见性
            // 让出cpu使用权
            // Thread.yield(); 
        }
        
        System.out.println(Thread.currentThread().getName() + "数据加载完成,跳出循环");
    }
    
    public static void main(String[] args) throws InterruptedException
    {
        VisibilityTest test = new VisibilityTest();
        
        // 线程threadA模拟数据加载场景
        Thread threadA = new Thread(() -> test.load(), "threadA");
        threadA.start();
        
        // 让threadA先执行一会儿后再启动线程B
        Thread.sleep(1000);
        
        // 线程threadB通过修改flag控制threadA的执行时间,数据加载可以结束了
        Thread threadB = new Thread(() -> test.refresh(), "threadB");
        threadB.start();
    }
    
    public static void shortWait(long interval)
    {
        long start = System.nanoTime();
        long end;
        do
        {
            end = System.nanoTime();
        } while (start + interval >= end);
    }
}

// Unsafe工具类
public class UnsafeFactory
{
    /**
     * 获取 Unsafe 对象
     */
    public static Unsafe getUnsafe()
    {
        try
        {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe)field.get(null);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * 获取字段的内存偏移量
     */
    public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName)
    {
        try
        {
            return unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName));
        }
        catch (NoSuchFieldException e)
        {
            throw new Error(e);
        }
    }
}
如何保证可见性
  • 通过 volatile 关键字保证可见性

  • 通过内存屏障保证可见性

  • 通过 synchronized 关键字保证可见性

  • 通过 Lock锁保证可见性

思考:为什么多线程对共享变量的操作存在可见性问题?

有序性

程序执行的顺序按照代码的先后顺序执行。为了提升性能,编译器和处理器常常会对指令做重排序,所以存在有序性问题。

有序性案例:

// 从代码顺序看,不会出现(0,0)的结果。指令重排序就有可能出现
public class ReOrderTest
{
    private static int x = 0, y = 0;
    
    private static int a = 0, b = 0;
    
    public static void main(String[] args) throws InterruptedException
    {
        int i = 0;
        while (true)
        {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            
            /**
             * x,y的值是多少: 0,1 1,0 1,1 0,0
             */
            Thread thread1 = new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    a = 1;
                    x = b;
                }
            });
            Thread thread2 = new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    b = 1;
                    y = a;
                }
            });
            
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            
            System.out.println("第" + i + "次(" + x + "," + y + ")");
            if (x == 0 && y == 0)
            {
                break;
            }
        }
    }
}
如何保证有序性
  • 通过 volatile 关键字保证有序性

  • 通过内存屏障保证有序性

  • 通过 synchronized关键字保证有序性

  • 通过Lock锁保证有序性

Java内存模型

在并发编程中,需要处理的两个关键问题:

1)多线程之间如何通信(线程之间以何种机制来交换数据)

2)多线程之间如何同步(控制不同线程间操作发生的相对顺序)

线程之间常用的通信机制有两种:共享内存和消息传递,Java采用的是共享内存模型

Java内存模型的抽象结构

Java线程之间的通信由Java内存模型(Java Memory Model,简称JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。

JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在,它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

根据JMM的规定,线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存中读取。JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序提供内存可见性的保证。

主内存与工作内存交互协议

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

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

相关文章

【题解 树形dp 拆位】 树上异或

「KDOI-06-S」树上异或 题目描述 给定一棵包含 n n n 个节点的树&#xff0c;第 i i i 个点有一个点权 x i x_i xi​。 对于树上的 n − 1 n-1 n−1 条边&#xff0c;每条边选择删除或不删除&#xff0c;有 2 n − 1 2^{n-1} 2n−1 种选择是否删除每条边的方案。 对于…

Vue中的响应式原理是如何实现的?

Vue中的响应式原理是通过使用Vue的响应式系统来实现的。这个系统依赖于JavaScript的Object.defineProperty方法&#xff0c;以及ES6的Proxy对象&#xff08;在Vue 3中&#xff09;。 下面是Vue中响应式原理的概述&#xff1a; 1&#xff1a;数据初始化&#xff1a; 在Vue组件…

【面试HOT100】链表树

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招面试的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于LeetCodeHot100进行的&#xff0c;每个知识点的修正和深入主要参考…

Java面向对象(基础)--方法应用

文章目录 一、方法的重载介绍案例&#xff08;1&#xff09;案例1 练习&#xff08;1&#xff09;练习1&#xff08;2&#xff09;练习2&#xff08;3&#xff09;练习3&#xff08;4&#xff09;练习4 二、可变个数形参的方法介绍举例&#xff08;1&#xff09;举例1&#xff…

计算机组成原理 new07 真值和机器数 无符号整数 定点整数 定点小数 $\color{red}{Δ}$

文章目录 真值和机器数 无符号整数无符号整数的定义无符号整数的特征无符号整数的表示范围无符号整数的加法无符号数的减法 有符号整数(定点整数)有符号整数的定义原码原码的特点反码反码的特点补码补码的特点快速求解n位负数补码的方法为什么补码能够多表示一个范围(重点)变形…

java1.8新特性流

案例描述 今天跟着黑马程序员的视频&#xff0c;完成“瑞吉外卖”项目的菜品信息管理模块的时候&#xff0c;遇到了一个比较陌生的写法 用到了Java8的新特性 stream().map((item) -> {}).collect() List<DishDto> collect records.stream().map((item) -> {DishDt…

人工智能(5):深度学习简介

1 深度学习 —— 神经网络简介 深度学习&#xff08;Deep Learning&#xff09;&#xff08;也称为深度结构学习【Deep Structured Learning】、层次学习【Hierarchical Learning】或者是深度机器学习【Deep Machine Learning】&#xff09;是一类算法集合&#xff0c;是机器学…

MySQL表操作—存储

建表&#xff1a; mysql> create table sch( -> id int primary key, -> name varchar(50) not null, -> glass varchar(50) not null -> ); Query OK, 0 rows affected (0.01 sec) 插入数据&#xff1a; mysql> insert into sch (id,name,…

【微信小程序】6天精准入门(第5天:利用案例与后台的数据交互)附源码

一、什么是后台交互&#xff1f; 在小程序中&#xff0c;与后台交互指的是小程序前端与后台服务器之间的数据通信和请求处理过程。通过与后台交互&#xff0c;小程序能够获取服务器端的数据、上传用户数据、发送请求等。 小程序与后台交互可以实现数据的传输、用户认证、实时消…

Babylonjs学习笔记(一)——搭建基础场景

React typescript umi Babylonjs 搭建基础场景 yarn add --save babylonjs babylonjs-loaders 1、封装基础场景 import { Engine, Scene } from "babylonjs"; import { useEffect,useRef,FC } from "react"; import "./index.less"type Prop…

自用bat脚本,命令

redis配置环境变量后 关机脚本 redis-server --service-stop启动脚本 :: 注释 rem echo off cd /d d:\\Redis :: redis-cli :: shutdown :: exit :: netstat -ano |findstr "6639" :: taskkill /pid {pid} /F redis-server redis.windows.conf pausecmd中替代gr…

BFS专题8 中国象棋-马-无障碍

题目&#xff1a; 样例&#xff1a; 输入 3 3 2 1 输出 3 2 1 0 -1 4 3 2 1 思路&#xff1a; 单纯的BFS走一遍即可&#xff0c;只是方向坐标的移动变化&#xff0c;需要变化一下。 代码详解如下&#xff1a; #include <iostream> #include <vector> #include…

上次的那段代码后续

之前写了一篇文章&#xff0c;说是一个要修改一个代码&#xff0c;很多人评论说代码说得不清不楚&#xff0c;不过在评论说又解释了一波之后&#xff0c;大家至少对这个代码有理解了&#xff0c;至少知道这个代码是做什么事情了。 如果是你&#xff0c;会不会修改这段代码&…

数据结构初阶——时间复杂度

朋友们我们又见面了&#xff0c;今天我们来学习数据结构的时间复杂度&#xff0c;在讲数据结构之前&#xff0c;大家可能只知道我们学习的是数据结构&#xff0c;但是还是不知道数据结构的具体定义&#xff0c;其实就是在内存上的数据。然后我们就像通讯录一样对它进行增删查改…

Qt 目录操作(QDir 类)及展示系统文件实战 QFilelnfo 类介绍和获取文件属性项目实战

一、目录操作(QDir 类) QDir 类提供访问系统目录结构 QDir 类提供对目录结构及其内容的访问。QDir 用于操作路径名、访问有关路径和文件的信息以及操作底层文件系统。它还可以用于访问 Qt 的资源系统 Qt 使用“/”作为通用目录分隔符&#xff0c;与“/”在 URL 中用作路径分…

istio介绍(一)

1. 概念 1.1 虚拟服务 虚拟服务提供流量路由功能&#xff0c;它基于 Istio 和平台提供的基本的连通性和服务发现能力&#xff0c;让您配置如何在服务网格内将请求路由到服务 示例&#xff1a; apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata:nam…

高项.项目管理经验、理念、教训

一、项目管理的一些经验 管项目重在管理&#xff0c;而不是死抠无关紧要的技术细节等等。 真正的团队一定是11>2&#xff0c;要把重心放在凝聚团队协力&#xff0c;共同完成目标上。 项目的推进永远都是不确定性的&#xff0c;真正考验项目经理的是不断出现的需求变更和状…

vue重修之路由【上】

文章目录 单页应用程序: SPA - Single Page Application路由简介Vue Reouter简介VueRouter的使用&#xff08;52&#xff09;组件的存放目录问题组件分类存放目录 路由的封装抽离 单页应用程序: SPA - Single Page Application 单页面应用(SPA): 所有功能在 一个html页面 上 单…

常用的跨域解决方案有哪些?

在 Web 开发中,跨域是指在浏览器环境下,通过 JavaScript 代码从一个域名的网页去访问另一个域名的资源。由于同源策略的限制,跨域请求通常会被浏览器阻止,为了实现跨域访问,HTML5 提供了一些机制来解决这个问题。 以下是一些常用的跨域解决方案: 1:JSONP(JSON with P…

展馆导览系统之AR互动式导航与展品语音讲解应用

一、项目背景 随着科技的进步和人们对于文化、艺术、历史等方面需求的提升&#xff0c;展馆在人们的生活中扮演着越来越重要的角色。然而&#xff0c;传统的展馆导览方式&#xff0c;如纸质导览、人工讲解等&#xff0c;已无法满足参观者的多元化需求。为了提升参观者的体验&a…