• 多线程基础部分Part2


    目录

    线程安全

    Java内存模型(JMM)

    工作内存

    为什么线程会不安全

    保证线程安全的三大特性

    可见性

    原子性

    防止指令重排

    synchronized关键字:线程上"锁",保证安全

    什么是锁?

     互斥现象 

    线程获取对象锁的过程

    上锁操作如何保证线程安全

    synchronized代码块刷新内存

     synchronized的不同使用方式

    1.修饰类中的成员方法,锁的对象就是当前成员类对象

     2.修饰静态方法,锁的是当前class对象(全局唯一,相当于把类锁上了)

     3.修饰代码块,明确所的是哪个对象

    Java标准库中的线程安全类 

     volatile关键字

    1.可以保证共享变量可见性

    2.使用volatile修饰的变量相当于一个内存屏障


    线程安全

    Java内存模型(JMM)

    JMM是描述线程的工作内存(概念,并不真实存在,就是一系列CPU的寄存器或高速缓存)和主内存(真实存在的RAM)之间的关系。

    工作内存

    每个线程都有自己的工作内存,当访问共享变量时(类中的成员变量,常量,静态变量),会先将主内存中的共享变量值拷贝一份放到线程自己的工作内存中,之后对此共享变量的读取操作都是在当前工作内存中进行的。

    为什么线程会不安全

    当两个进程同时访问同一共享变量时,会出现各种各样的问题,因为每个线程实际上都是将共享变量加载到自己的工作内存中进行各种操作的,这就导致了,各个线程之间的工作内存可能会出差错,有可能数据在主内存更新后,某一个线程的工作内存仍保存着之前还未更新的数据,并且用这个数据进行操作再写回主内存。也有可能,其他线程此刻还没有加载共享变量,此时从主内存读共享变量值有可能还没读到新修改的值。(脏读)

    保证线程安全的三大特性

    可见性

    一个线程对于共享变量的修改可以让其他线程立刻感知并可见(synchronized-上锁,volatile关键字,final关键字也可以保证可见性)

    原子性

    一个操作在进行中,无法被中断和打扰称为原子性,例如:

    int i = 10; 这行代码就是原子性的,因为它顺时发生,没有被打断的机会。

    i++ ; 就不是原子性的,因为在它加的时候可能会被别的线程读取。

    防止指令重排

    简单理解来说就是,在一个方法中,不影响最终结果的代码顺序是随意的,这种现象称为指令重排,但是如果在多线程中则会出现各种问题,例如此图:

    synchronized关键字:线程上"锁",保证安全

    synchronized-监视器锁 monitor lock

    什么是锁?

    举一个不是很恰当的例子,我们可把每一个线程比作一个人,把上锁的代码块比作厕所,若这些个线程需要上同一个厕所,那么可以假设先进去一个线程,进去之后线程就会把这个厕所的门锁上,这期间别的线程想进也是进不去的,这个就是线程间的互斥现象,只有等这个线程出来才能进去。

    如果线程间不需要抢同一个厕所,那么就可以说是不存在互斥问题,因为一个线程进一个厕所上不同的锁。

     互斥现象 

    当某个线程需要获取对象的锁时,其他线程若也要获取同一个线程的锁,就会处于阻塞状态。

    一定要注意,互斥现象只发生在线程对于同一对象的操作,不同的对象间是不存在互斥现象的。

    线程获取对象锁的过程

    每个线程进入synchronized代码块,都会尝试执行加锁操作。

    退出代码块的时候,就是释放这个锁。

    上锁操作如何保证线程安全

    synchronized代码块刷新内存

    线程执行synchronized代码块的流程

    a.获取对象锁

    b.从主内存拷贝变量值到工作内存

    c.执行代码

    d.将更改后的值写回主内存

    e.释放对象锁

    因为在同一时间内,只有一个线程能进入上锁代码,保证互斥,此时这个代码块是一个单线程操作,不涉及线程不安全的问题。所以上锁操作也是天然的原子性和可见性的体现。

     synchronized的不同使用方式

    1.修饰类中的成员方法,锁的对象就是当前成员类对象

     2.修饰静态方法,锁的是当前class对象(全局唯一,相当于把类锁上了)

     此时只要调用此类对象,同一时间只有一个线程可以执行increase2方法

     3.修饰代码块,明确所的是哪个对象

    锁的粒度更细,用的最多,只要在需要锁上的若干代码加上synchronzied关键字即可

    我们可以通过this锁定每一个对象

     也可以通过.class直接锁定整个类

     甚至可以自己来传递参数,自定义线程间的互斥关系

    Java标准库中的线程安全类 

    我们之前学习的集合类在多线程中都是不安全的

     所以为了保证线程安全,我们可以使用如下几种类:

    这些类全部来自java.util.concurrent类(Java并发工具包)

     这些类可以保证线程安全,例如:

    ConcurrentHashMap类:把方法全部上了锁

     volatile关键字

    1.可以保证共享变量可见性

    volatile关键字可以强制线程读写主内存的变量值

    相当于普通的共享变量,此关键字可以保证共享变量的可见性

    a.当线程读取的是此关键字修饰的内容时,线程直接从主内存读取该值到工作内存,无论当前工作内存是否由该值

    b.当线程写此关键字变量时,将当前修改后的变量值立刻从工作内存中刷新到主内存,并且在此过程中其他线程会等待(不是阻塞),直到写回主内存的操作完成,保证读的一定是刷新后的值

    对于同一个volatile变量,他的写操作一定是发生在读操作前的,保证读到的是主内存刷新后的数据。

    volatile只能保证可见性,不能保证原子性,所以如果线程不是原子性操作依旧不安全。

    2.使用volatile修饰的变量相当于一个内存屏障

    volatile修饰的代码可以防止指令重排,也就是它一定是在前面的代码执行结束后,后面的代码执行前执行,无论CPU觉得哪种方式更优,volatile修饰的代码执行位置是固定的。

  • 相关阅读:
    PyTorch(四)数据转换与构建神经网络
    【面试题】JS第七种数据类型Symbol详解
    为什么说继承是把双刃剑
    基于iNeuOS工业互联网平台的板材实时质检系统
    kickstarter众筹运营分享
    mysql读取文件
    IDEA 2023.3.6 下载、安装、激活与使用
    SAP ME21N\ME22N\ME23N采购订单增强:抬头、行项目取值处理
    信息学奥赛一本通 2076:【21CSPJ普及组】网络连接(network) | 洛谷 P7911 [CSP-J 2021] 网络连接
    浅析能耗管理系统在企业中的应用
  • 原文地址:https://blog.csdn.net/weixin_65278827/article/details/125464364