精华内容
下载资源
问答
  • 线程安全三要素

    2020-12-28 00:23:22
    一、原子性 ...可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其它线程可以立即看到修改后的结果。 、有序生 有序性指的是程序的执行顺序按照代码的先后顺序来执行。 ...

    一、原子性

    原子性指的是一个或多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。

    二、可见性

    可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其它线程可以立即看到修改后的结果。

    三、有序生

    有序性指的是程序的执行顺序按照代码的先后顺序来执行。

    展开全文
  • JAVA 线程安全三要素

    千次阅读 2018-03-06 21:15:11
    第1条规则程序顺序规则是说在一个线程里,所有的操作都是按顺序的,但是在JMM里其实只要执行结果一样,是允许重排序的,这边的happens-before强调的重点也是单线程执行结果的正确性,但是无法保证多线程也是如此。...

     

    (转载)

    1 . 原子性(Atomicity)

     Java中,对基本数据类型的读取和赋值操作是原子性操作,所谓原子性操作就是指这些操作是不可中断的,要做一定做完,要么就没有执行。

    比如:

    i = 2; //原子
    j = i; //非原子 (1 读i,2 赋值j)
    i++;   //非原子 (1 读i,2 +1 ,3 赋值i
    i = i + 1//非原子 (1 读i,2 +1 ,3 赋值i
    

    上面4个操作中,i=2是读取操作,必定是原子性操作,j=i你以为是原子性操作,其实吧,分为两步,一是读取i的值,然后再赋值给j,这就是2步操作了,称不上原子操作,i++i = i + 1其实是等效的,读取i的值,加1,再写回主存,那就是3步操作了。所以上面的举例中,最后的值可能出现多种情况,就是因为满足不了原子性。

    这么说来,只有简单的读取,赋值是原子操作,还只能是用数字赋值,用变量的话还多了一步读取变量值的操作。有个例外是,虚拟机规范中允许对64位数据类型(long和double),分为2次32为的操作来处理,但是最新JDK实现还是实现了原子操作的。

    JMM只实现了基本的原子性,像上面i++那样的操作,必须借助于synchronizedLock来保证整块代码的原子性了。线程在释放锁之前,其他线程不能取得锁,释放锁时必然会把i的值刷回到主存的。

    除了上锁还有CAS操作同样保证原子性。

    2 . 可见性(Visibility)

    JMM中每一个线程都有自己的独立工作内存区,还有一个公共内存区(主内存)。线程执行时先把变量值从主内存拷贝到自己工作内存区中。可见性的目的就是保证一个线程改变了变量后,其他线程及时刷新自己的工作区内存获取最新值。


    Java就是利用volatile来提供可见性的。当一个变量被volatile修饰时,对它的修改会立刻刷新到主存,并将其他线程缓存中的此变量清空,当其它线程需要读取该变量时,会去主内存中读取新值。而普通变量则不能保证每次都去主内存读取到最新值,会有延迟。

    其实通过synchronized和Lock也能够保证可见性,线程在释放锁之前,会把变量值都刷回主存,且在持锁后首先从主内存同步所有工作内存变量,但是和Lock的开销都更大。

    锁示例:this.isBegin即使不在同步代码块中,在线程下次持有锁时,也会用主内存读取this.isBegin的值,即线程刷新自己的工作区内存。

    while(this.isBegin){
        synchronized(o){
          ...
        }
    }

    3 . 有序性(Ordering)

    lock/unlock, volatile关键字可以产生内存屏障,防止指令重排序时越过

    JMM是允许编译器和处理器对指令重排序的,但是规定了as-if-serial语义,即不管怎么重排序,程序的执行结果不能改变。比如下面的程序段:

    double pi = 3.14;    //A
    double r = 1;        //B
    double s= pi * r * r;//C
    

    上面的语句,可以按照A->B->C执行,结果为3.14,但是也可以按照B->A->C的顺序执行,因为A、B是两句独立的语句,而C则依赖于A、B,所以A、B可以重排序,但是C却不能排到A、B的前面。JMM保证了重排序不会影响到单线程的执行,但是在多线程中却容易出问题。

    比如这样的代码:

    int a = 0;
    bool flag = false;
    
    public void write() {
        a = 2;              //1
        flag = true;        //2
    }
    
    public void multiply() {
        if (flag) {         //3
            int ret = a * a;//4
        }
        
    }
    

    假如有两个线程执行上述代码段,线程1先执行write,随后线程2再执行multiply,最后ret的值一定是4吗?结果不一定:

     

    如图所示,write方法里的1和2做了重排序,线程1先对flag赋值为true,随后执行到线程2,ret直接计算出结果,再到线程1,这时候a才赋值为2,很明显迟了一步。

     

    这时候可以为flag加上volatile关键字,禁止重排序,可以确保程序的“有序性”,也可以上重量级的synchronized和Lock来保证有序性,它们能保证那一块区域里的代码都是一次性执行完毕的。

    另外,JMM具备一些先天的有序性,即不需要通过任何手段就可以保证的有序性,通常称为happens-before原则。<<JSR-133:Java Memory Model and Thread Specification>>定义了如下happens-before规则:

    1. 程序顺序规则: 一个线程中的每个操作,happens-before于该线程中的任意后续操作
    2. 监视器锁规则:对一个线程的解锁,happens-before于随后对这个线程的加锁
    3. volatile变量规则: 对一个volatile域的写,happens-before于后续对这个volatile域的读
    4. 传递性:如果A happens-before B ,且 B happens-before C, 那么 A happens-before C
    5. start()规则: 如果线程A执行操作ThreadB_start()(启动线程B) , 那么A线程的ThreadB_start()happens-before 于B中的任意操作
    6. join()原则: 如果A执行ThreadB.join()并且成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
    7. interrupt()原则: 对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测是否有中断发生
    8. finalize()原则:一个对象的初始化完成先行发生于它的finalize()方法的开始

    第1条规则程序顺序规则是说在一个线程里,所有的操作都是按顺序的,但是在JMM里其实只要执行结果一样,是允许重排序的,这边的happens-before强调的重点也是单线程执行结果的正确性,但是无法保证多线程也是如此。

    第2条规则监视器规则其实也好理解,就是在加锁之前,确定这个锁之前已经被释放了,才能继续加锁。

    第3条规则,就适用到所讨论的volatile,如果一个线程先去写一个变量,另外一个线程再去读,那么写入操作一定在读操作之前。

    第4条规则,就是happens-before的传递性。

    后面几条就不再一一赘述了。

    展开全文
  • 多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。 解决方案 1)JMM 提供了 volatile 2.有序性 若在本线程内观察,所有操作是有有序的 若在一个线程观察另一个线程,所有操作...

    并发编程的本质其实就是利用多线程技术,在现代多核的CPU的背景下,催生了并发编程的趋势,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。除此之外,面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分 。

    即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现 这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。

    并发不等于并行:并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。实际上,如果系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行, 只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。

    并发的优点:

    1. 充分利用多核CPU的计算能力
    2. 方便进行业务拆分,提升应用性能;

    并发产生的问题:

    1. 高并发场景下,导致频繁的上下文切换
    2. 临界区线程安全问题,容易出现死锁的,产生死锁就会造成系统功能不可用
    3. 其它

    CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

    在这里插入图片描述

    1.可见性

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

    解决方案

    1)JMM 提供了 volatile

    2)synchronized(当程序除了要保证可见性,还要保证原子性时)

    2.有序性

    • 若在本线程内观察,所有操作是有有序的
    • 若在一个线程观察另一个线程,所有操作时无序的
    • 在 JVM 中,为了效率允许编译器和处理器对指令进行重排序

    解决方案

    1)Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性

    • as-if-seria(线程内)

      • 指令重排必须保证,单线程内重排序后执行结果不变。
      • 为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。
    • happens-before(多线程)(JDK5,JSR-133内存模型)

      • 程序顺序规则:一个线程中的每个操作,happens-before于该线程任意后续操作
      • start()规则:线程的start()方法先于它的每一个动作,即如果线程A在执行线程B的 start方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量 的修改对线程B可见
      • join()规则:Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见。
      • volatile变量规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性,简单 的理解就是,volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当 该变量发生变化时,又会强迫将新的值刷新到主内存,任何时刻,不同的线程总是能 够看到该变量的新值
      • 监视器锁规则:解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说, 如果对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动作之后(同一个锁)
      • 传递性:如果A happens-before B,B happens-before C ,那么 A happens-before C
      • 线程中断规则:对线程 interrupt()方法的调用先行发生于被中断线程的代码检测到中 断事件的发生,可以通过Thread.interrupted()方法检测线程是否中断
      • 对象终结规则:对象的构造函数执行,结束先于finalize()方法

    2)还可以使用 volatile 和 synchronized 两个关键字来保证有序性(当默认的规则无法实现时,比如DCL单例)

    3.原子性

    一个线程执行一段代码时不被打断,要么都成功,要么都失败

    注意:我们可以大致认为基本类型变量的读写是具备原子性的

    解决方案

    1)悲观锁:synchronized 关键字,JVM 级别锁

    注意:由于JMM 定义的 lock 相关规则,synchronized 除了保证原子性还能保证可见性和有序性

    • 可见性:对一个变量执行unlock操作之前,必须先把此变量同步回主内存中,即执行store和write操作
    • 有序性:一个变量同一时刻只允许一条线程对其进行lock操作

    2)乐观锁:自旋+CAS(比如 AtomicInteger 的自增操作)

    注意:这种方案无法保证可见性,一般配合 volatile 使用。

    展开全文
  • 线程安全和多线程三要素

    千次阅读 2019-11-04 14:18:27
    一、线程安全 1.什么是线程安全 线程安全就是当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为...

     一、线程安全

    1.什么是线程安全

        线程安全就是当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
        - 通俗来讲,如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的,或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题 。
     

    2. 线程安全级别

    - 线程安全也是有几个级别
        - 不可变:
            - 像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
        - 绝对线程安全
            - 不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet
        - 相对线程安全
            - 相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。
        - 线程非安全
            - ArrayList、LinkedList、HashMap等都是线程非安全的类.


    3.线程安全需要保证几个基本特性


        - 1、原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
        - 2、可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile 就是负责保证可见性的。
        - 3、有序性,是保证线程内串行语义,避免指令重排等。


    二、多线程三要素

    1 .三要素分别是啥

    - 三要素分别是:原子性,可见性,有序性

     2. 如何理解原子性

      即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。
     

    3.如何理解可见性

     - 当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。


     4. 如何理解有序性

     - 顺序性指的是,程序执行的顺序按照代码的先后顺序执行。
     

    三、如何实现线程安全

    保证线程安全可从多线程三特性出发:
        - 原子性(Atomicity):单个或多个操作是要么全部执行,要么都不执行
            - Lock:保证同时只有一个线程能拿到锁,并执行申请锁和释放锁的代码
            - synchronized:对线程加独占锁,被它修饰的类/方法/变量只允许一个线程访问
        - 可见性(Visibility):当一个线程修改了共享变量的值,其他线程能够立即得知这个修改
            - volatile:保证新值能立即同步到主内存,且每次使用前立即从主内存刷新;
            - synchronized:在释放锁之前会将工作内存新值更新到主存中
        - 有序性(Ordering):程序代码按照指令顺序执行
            - volatile: 本身就包含了禁止指令重排序的语义
            - synchronized:保证一个变量在同一个时刻只允许一条线程对其进行lock操作,使得持有同一个锁的两个同步块只能串行地进入

    展开全文
  • 当多个线程要共享一个实例对象的值得时候,那么在考虑安全的多线程并发编程时就要保证下面3个要素:原子性(Synchronized, Lock)有序性(Volatile,...所以是线程安全的,也可以实现这一功能,但是由于线程是同...
  • 影响java线程安全个因素

    千次阅读 2018-11-09 14:09:02
    java的线程安全性 ...线程安全性主要体现在个方面: 原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作 可见性:一个线程对主内存的修改可以及时的被其他线程观察到。 有序性:一个线程观察...
  • 在集合这一块,线程安全和线程不安全一直是一个比较疑惑的点。 在这个问题的基础下,首先我们要先记住集合中那些容器...存在线程安全问题必须满足个条件: 1.有共享变量 2.处在多线程环境下 3.共享变量有修改操作 .
  • 线程安全性: 当多个线程访问某个类,不管运行环境采用何种调度方式或者这些进程将如何调用,并且在主调代码中不需要额外的同步或协同,都能表现出正确的行为,这就是线程安全的。主要体现在个方面:原子性:互斥...
  • 线程间协作、保障线程安全的设计技术
  • 前面几篇已经介绍了关于...在设计线程安全类的过程中,需要包含以下个基本要素: 找出构成对象状态的所有变量。 找出约束状态变量的不变性条件。 建立对象状态的并发访问策略。 实例封闭 将数据封装在对象...
  • 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的二、线程安全三要素:...
  • 线程安全-sychronized

    2019-06-06 05:28:57
    造成线程数据错乱的三要素 ( 同时也是保持线程安全要素 ) 名词解释 原子性(Synchronized, Lock)即一个操作或者多个操作,要么执行 要么就都不执行,在执行过程中不可打断 有序性 (Volatile...
  • 设计线程安全类的过程中需要注意个基本要素: 1、找出构成对象的所有变量 2、找出约束状态变量的不变性条件 3、建立对象状态的并发访问管理策略 Java5.0提供了多种并发容器来改进同步容器的性能。同步容器将所有对...
  • 封装设计: 尽管所有的状态都存储在公共静态变量(域)中,仍然能写出线程安全...=====设计线程安全类的过程应该包括下面3个基本要素:======== 1、确定对象状态是由哪些变量组成的; 2、确定限制状态变量的不变约束;
  • 到目前为止,我们已经介绍了关于线程安全与同步的一些基础知识。然而,我们并不希望对每一次内存访问都进行分析以确保是线程安全的,而是希望将一些现有的线程安全组件组合为更大规模的组合为更大规模的组件或程序。...
  • 学习书籍《Java concurrency in practice》设计线程安全类,个基本要素: 找出构成对象状态的所有变量 找出约束状态变量的不变性条件 简历对象状态的并发访问管理策略 1.使用java监视器模式(也就是synchronized模式)...
  • JAVA实现线程安全

    2017-03-16 16:34:50
    保证线程安全种方法:[/b] 不要跨线程访问共享变量 使共享变量是final类型的 将共享变量的操作加上同步 一开始就将类设计成线程安全的, 比在后期重新修复它,更容易. 编写多线程程序, 首先保证它是正确的, ...
  • 目录:线程并发问题&线程安全&...线程并发时需要保证线程安全,需要满足大条件: 原子性 可见性 有序性 原子性(Atomicity) 对于一条线程执行来说要保证,要么都成功,要么都失败;对于原子性
  • 单例模式的6种实现及各实现的优缺点3.1 懒汉式(线程不安全)3.2 饿汉式(线程安全)3.3 懒汉式(线程安全)3.4 双重检查锁实现(线程安全)3.5 静态内部类实现(线程安全)3.6 枚举类实现(线程安全)4....
  • 在JAVA中ArrayList如何保证线程安全

    万次阅读 2016-11-03 11:17:35
    保证线程安全种方法: 不要跨线程访问共享变量 使共享变量是final类型的 将共享变量的操作加上同步 一开始就将类设计成线程安全的, 比在后期重新修复它,更容易. 编写多线程程序, 首先保证它是正确的, 其次再...
  • 深入浅出Java多线程

    千次阅读 2020-02-24 17:30:34
    文章目录线程1.1 线程与进程的区别1.2 线程的状态1.3 Notify 和 wait :1.4 Thread.sleep() 和Thread.yield()的异同1.5 补充:死锁的概念1.6 补充:并发和并行的区别1.7 补充:线程安全三要素1.8 补充:如何实现线程...
  • 大家可以回顾下线程安全构成的三要素: 1,多线程环境 2,访问同一个资源 3,资源具有状态性 那么Spring的bean模式是单例,而且后端的程序,天然就处于一个多线程的工作环境。 那么是安全的吗? 关键看第3点,我们...
  • 在设计线程安全类的过程中,需要包含以下个基本要素: 1、找出构成对象状态的所有变量 2、找出约束状态变量的不变性条件 3、建立对象状态的并发访问管理策略 ps: 一言以蔽之,让所有变量都被对象并发管理。 1. ...
  • 如何做到java线程安全

    千次阅读 2014-09-23 08:41:36
    [b]保证线程安全种方法:[/b] 不要跨线程访问共享变量 使共享变量是final类型的 将共享变量的操作加上同步 一开始就将类设计成线程安全的, 比在后期重新修复它,更容易. 编写多线程程序, 首先保证它是正确的,...
  • 设计线程安全类的个基本要素: 找出构成对象的所有变量 找出所有约束所有变量的不变性条件,简单说就是变量的正确限制。一个变量在整个类的逻辑中对其有什么样的限制,比如必须大于0等等条件。 建立对象的并发...
  • package com.qianfeng.test; public class Demo { public static void main(String[] args) { Test test = new Test(); Thread thread = new Thread(test);...//单例模式三要素: // 1.私有的构造...
  • 研读【Java并发编程实践】,对于线程安全性、对象的共享和对象的组合这部分内容整理了相关读书笔记,主要内容是并发性和线程安全性的基本概念,介绍了避免并发危险、构建线程安全的类以及验证线程安全的规则,同时...
  • 将现有的线程安全的组件组合为更大规模的组件或程序。 通过使用封装技术可以使得在不对整个程序进行分析的情况下就可以判断一个类是否是线程安全的。 一. 基本要素 1.1 找出对象状态的所有变量 如果对象中所有的域...
  • 设计线程安全的类设计线程安全类的个基本要素: 1. 找出构成对象状态的所有变量 2. 找出约束状态变量的不变性条件 3. 建立对象状态的并发访问管理策略要分析对象的状态,首先从对象的域开始。如果对象所有的域...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,209
精华内容 8,083
关键字:

线程安全的三要素