精华内容
下载资源
问答
  • 并发三大特性

    2019-10-03 16:45:47
    并发三大特性 1. 原子性 2. 可见性 3. 有序性 并发三大特性 原子性、可见性、有序性 1. 原子性 含义 一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。 在 Java 中,对基本...

    并发三大特性

    原子性、可见性、有序性

    1. 原子性

    含义

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

    在 Java 中,对基本数据类型的变量读取赋值操作是原子性操作。

    重要

    不采取任何的原子性保障措施的自增操作并不是原子性的。

    如何保证原子性
    • 通过 synchronized 关键字定义同步代码块或者同步方法保障原子性。
    • 通过 Lock 接口保障原子性。
    • 通过 Atomic 类型保障原子性。

    2. 可见性

    含义

    当一个线程修改了共享变量的值,其他线程能够看到修改的值。

    Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性的。

    volatile 变量和普通变量区别

    普通变量与 volatile 变量的区别是 volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新,因此我们可以说 volatile 保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。

    如何保证可见性
    • 通过 volatile 关键字标记内存屏障保证可见性。
    • 通过 synchronized 关键字定义同步代码块或者同步方法保障可见性。
    • 通过 Lock 接口保障可见性。
    • 通过 Atomic 类型保障可见性。
    • 通过 final 关键字保障可见性

    3. 有序性

    含义

    即程序执行的顺序按照代码的先后顺序执行。

    JVM 存在指令重排,所以存在有序性问题。

    如何保证有序性
    • 通过 synchronized关键字 定义同步代码块或者同步方法保障可见性。
    • 通过 Lock接口 保障可见性。

    转载于:https://www.cnblogs.com/weixuqin/p/11424688.html

    展开全文
  • JAVA并发三大特性

    2021-03-28 23:23:20
    1、原子性 2、可见性 3、有序性

    1、原子性

    2、可见性

    3、有序性

    展开全文
  • 并发编程三大特性

    千次阅读 2018-09-16 19:56:19
    并发编程中往往涉及个问题:原子性、可见性、有序性。 原子性 定义:即一个或者多个操作作为一个整体,要么全部执行,要么都不执行,并且操作在执行过程中不会被线程调度机制打断;而且这种操作一旦开始,就一直...

    并发编程中往往涉及三个问题:原子性、可见性、有序性。

    原子性

    定义:即一个或者多个操作作为一个整体,要么全部执行,要么都不执行,并且操作在执行过程中不会被线程调度机制打断;而且这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换。

    例如转账问题,A向B转1000元,该过程分解成两个步骤:

    1、A账户减掉1000元;

    2、B账户增加1000元。

    上述两个步骤如果中途被打断会造成什么后果?A账户已经被扣掉1000了,但是B账户确没有收到1000,这种情况肯定要禁止的。所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。

    在代码编程中经常使用如下语句:

    1. i = 0;       //1
    2. j = i ;      //2
    3. i++;         //3
    4. i = j + 1;   //4

    分析上述四种语句,哪些是原子操作,哪些不是。

    1在Java中,对基本数据类型的变量和赋值操作都是原子性操作;

    2中包含了两个操作:读取i,将i值赋值给j

    3中包含了三个操作:读取i值、i + 1 、将+1结果赋值给i;

    4中同三一样

    从上面分析中,其实可以初步总结出,语句中包含多个操作的情况,通常都不具备原子性,那么在实际开发过程中如果只是单线程的话是没问题的,但是多线程就很有可能得到意想不到的结果了。主要原因就是在操作过程中其它线程中途可能会写入数据,导致读取到的可能是脏数据。

    要想在多线程环境下保证原子性,则可以通过锁、synchronized来确保。volatile是无法保证复合操作的原子性。

    可见性

    定义:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

    例子:

    //线程1执行的代码
    int i = 0;
    i = 10;

    //线程2执行的代码
    j = i;

    如果执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i = 10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有立即写入到主存当中。此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10。这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值。

    在上面已经分析了,在多线程环境下,一个线程对共享变量的操作对其它线程是不可见的。

    有序性

    定义:即程序执行的顺序按照代码的先后顺序执行。

    例子:

    private int i = 0;              
    private bool flag = false;
    i = 1;                //语句1  
    flag = true;          //语句2

    从代码顺序上看,语句1是在语句2前面的,但是JVM在真正执行这段代码的时候不会保证语句1一定会在语句2前面执行,因为这里可能会发生指令重排序(Instruction Reorder)。指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

    虽然处理器会对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同,那么它靠什么保证的呢?再看下面一个例子:
    int i = 0;    //语句1
    int j = 0;    //语句2
    i = i + 10;   //语句3
    j = i * i;    //语句4

    这段代码有4个语句,那么可能的一个执行顺序是:
    语句2 -> 语句1 -> 语句3 -> 语句4
    那么可不可能是这个执行顺序:
    语句2 -> 语句1 -> 语句4 -> 语句3。
    不可能,因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。虽然重排序不会影响单个线程内程序执行的结果,但是多线程呢?下面看一个例子:

    private boolean flag = false;
    private Context context = null;
    //线程1
    context = loadContext();  //语句1
    flag = true;              //语句2
    
    //线程2 
    while(!flag){
        Thread.sleep(1000L);
    }
    dowork(context);

    由于在线程1中语句1、语句2是没有依赖性的,所以可能会出现指令重排。如果发生了指令重排,线程1先执行语句2,这时候线程2开始执行,此时flag值为true,因此线程2继续执行dowrk(context),此时context并没有初始化,因此就会导致程序错误。

    从上面可以看出,指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。

    总结:要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

    展开全文
  • 并发编程中有个非常重要的特性:原子性、有序性,、可见性,学妹发现你对它们不是很了解,她很着急,因为理解这特性对于能够正确地开发高并发程序有很的帮助,接下来的面试中也极有可能被问到,小学妹就忍...

    在并发编程中有三个非常重要的特性:原子性、有序性,、可见性,学妹发现你对它们不是很了解,她很着急,因为理解这三个特性对于能够正确地开发高并发程序有很大的帮助,接下来的面试中也极有可能被问到,小学妹就忍不住开始跟你逐一介绍起来。

    Java内存模型

    在讲三大特性之前先简单介绍一下Java内存模型(Java Memory Model,简称JMM),了解了Java内存模型以后,可以更好地理解三大特性。

    Java内存模型是一种抽象的概念,并不是真实存在的,它描述的是一组规范或者规定。JVM运行程序的实体是线程,每一个线程都有自己私有的工作内存。Java内存模型中规定了所有变量都存储在主内存中,主内存是一块共享内存区域,所有线程都可以访问。但是线程对变量的读取赋值等操作必须在自己的工作内存中进行,在操作之前先把变量从主内存中复制到自己的工作内存中,然后对变量进行操作,操作完成后再把变量写回主内存。线程不能直接操作主内存中的变量,线程的工作内存中存放的是主内存中变量的副本。

    原子性(Atomicity)

    什么是原子性

    原子性是指:在一次或者多次操作时,要么所有操作都被执行,要么所有操作都不执行。

    一般说到原子性都会以银行转账作为例子,比如张三向李四转账100块钱,这包含了两个原子操作:在张三的账户上减少100块钱;在李四的账户上增加100块钱。这两个操作必须保证原子性的要求,要么都执行成功,要么都执行失败。不能出现张三的账户减少100块钱而李四的账户没增加100块钱,也不能出现张三的账户没减少100块钱而李四的账户却增加100块钱。

    原子性示例

    示例一
    i = 1;
    

    根据上面介绍的Java内存模型,线程先把i=1写入工作内存中,然后再把它写入主内存,就此赋值语句可以说是具有原子性。

    示例二
    i = j;
    

    这个赋值操作实际上包含两个步骤:线程从主内存中读取j的值,然后把它存入当前线程的工作内存中;线程把工作内存中的i改为j的值,然后把i的值写入主内存中。虽然这两个步骤都是原子性的操作,但是合在一起就不是原子性的操作。

    示例三
    i++;
    

    这个自增操作实际上包含三个步骤:线程从主内存中读取i的值,然后把它存入当前线程的工作内存中;线程把工作内存中的i执行加1操作;线程再把i的值写入主内存中。和上一个示例一样,虽然这三个步骤都是原子性的操作,但是合在一起就不是原子性的操作。

    从上面三个示例中,我们可以发现:简单的读取和赋值操作是原子性的,但把一个变量赋值给另一个变量就不是原子性的了;多个原子性的操作放在一起也不是原子性的。

    如何保证原子性

    在Java内存模型中,只保证了基本读取和赋值的原子性操作。如果想保证多个操作的原子性,需要使用synchronized关键字或者Lock相关的工具类。如果想要使int、long等类型的自增操作具有原子性,可以用java.util.concurrent.atomic包下的工具类,如:AtomicIntegerAtomicLong等。另外需要注意的是,volatile关键字不具有保证原子性的语义。

    可见性(Visibility)

    什么是可见性

    可见性是指:当一个线程对共享变量进行修改后,另外一个线程可以立即看到该变量修改后的最新值。

    可见性示例

    package onemore.study;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class VisibilityTest {
        public static int count = 0;
    
        public static void main(String[] args) {
            final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    
            //读取count值的线程
            new Thread(() -> {
                System.out.println("开始读取count...");
                int i = count;//存放count的更新前的值
                while (count < 3) {
                    if (count != i) {//当count的值发生改变时,打印count被更新
                        System.out.println(sdf.format(new Date()) + " count被更新为" + count);
                        i = count;//存放count的更新前的值
                    }
                }
            }).start();
    
            //更新count值的线程
            new Thread(() -> {
                for (int i = 1; i <= 3; i++) {
                    //每隔1秒为count赋值一次新的值
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(sdf.format(new Date()) + " 赋值count为" + i);
                    count = i;
    
                }
            }).start();
        }
    }
    

    在运行代码之前,先想一下运行的输出是什么样子的?在更新count值的线程中,每一次更新count以后,在读取count值的线程中都会有一次输出嘛?让我们来看一下运行输出是什么:

    开始读取count...
    17:21:54.796 赋值count为1
    17:21:55.798 赋值count为2
    17:21:56.799 赋值count为3
    

    从运行的输出看出,读取count值的线程一直没有读取到count的最新值,这是为什么呢?因为在读取count值的线程中,第一次读取count值时,从主内存中读取count的值后写入到自己的工作内存中,再从工作内存中读取,之后的读取的count值都是从自己的工作内存中读取,并没有发现更新count值的线程对count值的修改。

    如何保证可见性

    在Java中可以用以下3种方式保证可见性。

    使用volatile关键字

    当一个变量被volatile关键字修饰时,其他线程对该变量进行了修改后,会导致当前线程在工作内存中的变量副本失效,必须从主内存中再次获取,当前线程修改工作内存中的变量后,同时也会立刻将其修改刷新到主内存中。

    使用synchronized关键字

    synchronized关键字能够保证同一时刻只有一个线程获得锁,然后执行同步方法或者代码块,并且确保在锁释放之前,会把变量的修改刷新到主内存中。

    使用Lock相关的工具类

    Lock相关的工具类的lock方法能够保证同一时刻只有一个线程获得锁,然后执行同步代码块,并且确保执行Lock相关的工具类的unlock方法在之前,会把变量的修改刷新到主内存中。

    有序性(Ordering)

    什么是有序性

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

    在Java中,为了提高程序的运行效率,可能在编译期和运行期会对代码指令进行一定的优化,不会百分之百的保证代码的执行顺序严格按照编写代码中的顺序执行,但也不是随意进行重排序,它会保证程序的最终运算结果是编码时所期望的。这种情况被称之为指令重排(Instruction Reordering)。

    有序性示例

    package onemore.study;
    
    public class Singleton {
        private Singleton (){}
    
        private static boolean isInit = false;
        private static Singleton instance;
    
        public static Singleton getInstance() {
            if (!isInit) {//判断是否初始化过
                instance = new Singleton();//初始化
                isInit = true;//初始化标识赋值为true
            }
            return instance;
        }
    }
    

    这是一个有问题的单例模式示例,假如在编译期或运行期时指令重排,把isInit = true;重新排序到instance = new Singleton();的前面。在单线程运行时,程序重排后的执行结果和代码顺序执行的结果是完全一样的,但是多个线程一起执行时就极有可能出现问题。比如,一个线程先判断isInit为false进行初始化,本应在初始化后再把isInit赋值为true,但是因为指令重排没后初始化就把isInit赋值为true,恰好此时另外一个线程在判断是否初始化过,isInit为true就执行返回了instance,这是一个没有初始化的instance,肯定造成不可预知的错误。

    如何保证有序性

    这里就要提到Java内存模型的一个叫做先行发生(Happens-Before)的原则了。如果两个操作的执行顺序无法从Happens-Before原则推到出来,那么可以对它们进行随意的重排序处理了。Happens-Before原则有哪些呢?

    • 程序次序原则:一段代码在单线程中执行的结果是有序的。
    • 锁定原则:一个锁处于被锁定状态,那么必须先执行unlock操作后面才能进行lock操作。
    • volatile变量原则:同时对volatile变量进行读写操作,写操作一定先于读操作。
    • 线程启动原则:Thread对象的start方法先于此线程的每一个动作。
    • 线程终结原则:线程中的所有操作都先于对此线程的终止检测。
    • 线程中断原则:对线程interrupt方法的调用先于被中断线程的代码检测到中断事件的发生。
    • 对象终结原则:一个对象的初始化完成先于它的finalize方法的开始。
    • 传递原则:操作A先于操作B,操作B先于操作C,那么操作A一定先于操作C。

    除了Happens-Before原则提供的天然有序性,我们还可以用以下几种方式保证有序性:

    • 使用volatile关键字保证有序性。
    • 使用synchronized关键字保证有序性。
    • 使用Lock相关的工具类保证有序性。

    总结

    • 原子性:在一次或者多次操作时,要么所有操作都被执行,要么所有操作都不执行。
    • 可见性:当一个线程对共享变量进行修改后,另外一个线程可以立即看到该变量修改后的最新值。
    • 有序性:程序执行的顺序按照代码的先后顺序执行。

    synchronized关键字和Lock相关的工具类可以保证原子性、可见性和有序性,volatile关键字可以保证可见性和有序性,不能保证原子性。

    文章持续更新,微信搜索「 万猫学社 」第一时间阅读。
    关注后回复「 电子书 」,免费获取12本Java必读技术书籍。

    展开全文
  • 并发中的三大特性详解

    千次阅读 2020-05-21 22:57:04
    前言:Java并发编程的三大特性:原子性、可见性、有序性。要保证并发代码的安全性则必须满足这三大特性 原子性:一个或者多个操作,要么全部执行(执行的过程是不会被打断的)、要么全部不执行。 案例分析:...
  • 并发编程的书籍都会讲到并发编程的三大特性,这是并发编程中所有问题的根源,我们只有深刻理解了这三大特性,才不会编写出漏洞百出的并发程序。 基本概念 1、原子性,所有操作要么全部成功,要么全部失败。 2、...
  • 并发编程的三大特性

    2020-05-12 08:08:15
    原子性 狭义上指的是CPU操作指令必须是原子操作 广义上指的是字节码指令是原子操作 保证原子性? 加锁(Synchronize、Lock) 有序性 可见性
  • 在 Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例中,对并发中的特性(原子性、可见性和有序性)进行了初步学习。 本章主要就Java中保障原子性的技术进行更加全面的学习。 1.整体回顾 原子性...
  • 在 Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例中,对并发中的特性(原子性、可见性和有序性)进行了初步学习。 本章主要就Java中保障有序性的技术进行更加全面的学习。 1.整体回顾 有序性...
  • java并发编程的三大问题

    千次阅读 2019-03-13 15:02:47
    java并发编程的三大问题 背景: 计算机处理速度:CPU > 内存 > IO 为了平衡三者的速度差异:CPU 增加了缓存;操作系统增加了进程、线程,以分时复用 CPU;编译程序优化指令执行次序 可见性:一个线程对共享...
  • 本章主要学习Java并发中的特性:原子性、可见性和有序性。 在Java并发编程中,如果要保证代码的安全性,则必须保证代码的原子性、可见性和有序性。 本章的很多概念可以参考:Java并发11:Java内存模型、指令...
  • 在 Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例中,对并发中的特性(原子性、可见性和有序性)进行了初步学习。 本章主要就Java中保障可见性的技术进行更加全面的学习。 1.整体回顾 可见性...
  • 并发编程的特性

    2020-08-18 16:35:16
    并发编程中的特性 原子性 一次操作要么成功要么失败。 可见性 多线程操作一个变量时,一个线程修改了值,要对另外线程可见。 有序性 执行有先后顺序 volatile关键字:保证不同线程对share操作的可见性,禁止对...
  • 二、并发编程常见面试题 1、在java中守护线程和用户线程的区别? 2、线程与进程的区别 3、什么是多线程中的上下文切换 4、死锁与活锁的区别,死锁与饥饿的区别? 5、synchronized底层实现原理 6、什么是线程组...
  •          总结:  这就是并发编程的3种特性,而synchronized关键字在需要这3种特性时都可以作为一种解决方案,看起来很“万能”,但是越这样“万能”的并发控制,也是会伴随着一些性能的影响的(如线程越多...
  • 并发编程时,会出现可见性问题,当一个线程对共享变量进行了修改,另外的线程并没有立即看到修改后的最新值。 原子性概念 原子性(Atomicity):在一次或多次操作中,要么所有的操作都执行并且不会受其他因素干扰而...
  • 并发编程的个重要特征 原子性 : 一个操作或者多次操作,要么所有的操作全部都得到执行并且不会收到任何因素的干扰而中断,要么所有的操作都执行,要么都不执行。synchronized 可以保证代码片段的原子性。 可见性 ...
  • 首先了解一下并发编程的一些内容: 介绍线程之间读取数据的流程 原子性 可见性: 有序性: 开始介绍volatile关键字的作用: 但是!! volatile(应用场景)和synchronized关键字的...
  • 1.事务特性ACID 1.1.原子性Atomicity 原子性: 事务的所有操作,要么全部执行,要么全部不执行,不存在部分执行成功的情况。 如果执行过程中出错,则应该回滚rollback到事务开始前的状态。 事务是一个不可分割的...
  • Java并发编程的特性

    2015-11-08 20:45:42
    java并发编程的特性
  • 并发编程面试题(2020最新版)

    万次阅读 多人点赞 2020-03-14 17:28:01
    文章目录基础知识并发编程的优缺点为什么要使用并发编程(并发编程的优点)并发编程有什么缺点并发编程要素是什么?在 Java 程序中怎么保证多线程的运行安全?并行和并发有什么区别?什么是多线程,多线程的优劣?...
  • 1、原子性 所谓原子性,是指在一次操作或多次操作中,要么所有的操作全部执行,并不会受到人任何元素的干扰而中断,要么所有的操作都不执行。... 2、可见性 是指一个线程对共享变量进行了修改,另一个线程要能立马...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 419,692
精华内容 167,876
关键字:

并发三大特性