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

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

    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必读技术书籍。

    展开全文
  • 1、稀疏性的重要性,暂时只理解到以下几点: 1)稀疏性有利于突出重点 2)人脑神经的激活是稀疏的 3)稀疏性减少了信息量,有利于增加记忆量 4)稀疏的特征更加线性可分 5)计算量减小 6)属于一种正则化,...

    1、稀疏性的重要性,暂时只理解到以下几点:

    1)稀疏性有利于突出重点

    2)人脑神经的激活是稀疏的

    3)稀疏性减少了信息量,有利于增加记忆量

    4)稀疏的特征更加线性可分

    5)计算量减小

    6)属于一种正则化,减少了过拟合(不限制的话,一个输入有无数种编码可能)

    2、特征排列有序指的是:相似的输入(图像等)应该激活相邻的神经元。重要性:

    1)如果输出特征虽然是稀疏的,却并不平滑,不利于学习

    2)输出的编码能体现相互之间的相关性,对于分类器等有更好的作用

    3)通过局部归类强制输出的编码有序,避免刻意拟合数据,减少过拟合


    展开全文
  • 原子、可见有序性是多线程编程中最重要的几个知识点,由于多线程情况复杂,如何让每个线程能看到正确结果,这是非常重要的。 原子 原子是指一个线程操作是不能被其他线程打断,同一时间只有一个线程...

    原子性、可见性、有序性是多线程编程中最重要的几个知识点,由于多线程情况复杂,如何让每个线程能看到正确的结果,这是非常重要的。

    原子性

    原子性是指一个线程的操作是不能被其他线程打断,同一时间只有一个线程对一个变量进行操作。在多线程情况下,每个线程的执行结果不受其他线程的干扰,比如说多个线程同时对同一个共享成员变量n++100次,如果n初始值为0,n最后的值应该是100,所以说它们是互不干扰的,这就是传说的中的原子性。但n++并不是原子性的操作,要使用AtomicInteger保证原子性。

    可见性

    可见性是指某个线程修改了某一个共享变量的值,而其他线程是否可以看见该共享变量修改后的值。在单线程中肯定不会有这种问题,单线程读到的肯定都是最新的值,而在多线程编程中就不一定了。

    每个线程都有自己的工作内存,线程先把共享变量的值从主内存读到工作内存,形成一个副本,当计算完后再把副本的值刷回主内存,从读取到最后刷回主内存这是一个过程,当还没刷回主内存的时候这时候对其他线程是不可见的,所以其他线程从主内存读到的值是修改之前的旧值。

    像CPU的缓存优化、硬件优化、指令重排及对JVM编译器的优化,都会出现可见性的问题。

    有序性

    我们都知道程序是按代码顺序执行的,对于单线程来说确实是如此,但在多线程情况下就不是如此了。为了优化程序执行和提高CPU的处理性能,JVM和操作系统都会对指令进行重排,也就说前面的代码并不一定都会在后面的代码前面执行,即后面的代码可能会插到前面的代码之前执行,只要不影响当前线程的执行结果。所以,指令重排只会保证当前线程执行结果一致,但指令重排后势必会影响多线程的执行结果。

    虽然重排序优化了性能,但也是会遵守一些规则的,并不能随便乱排序,只是重排序会影响多线程执行的结果。

    明天我再讲讲volatile关键字,它能保证可见性、有序性,但不能保证原子性。

    推荐去我的博客阅读更多:

    1.Java JVM、集合、多线程、新特性系列教程

    2.Spring MVC、Spring Boot、Spring Cloud 系列教程

    3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

    4.Java、后端、架构、阿里巴巴等大厂最新面试题

    觉得不错,别忘了点赞+转发哦!

    展开全文
  • 之前项目中由于用到了Kafka,所以在前一阵实习面试中被问到了如何保证Kafka消息的有序性,所以本文对于Kafka这三个常被问到问题进行总结归纳。 可靠 对于消息队列而言,如果不能保证消息可靠可能会引起...

    之前的项目中由于用到了Kafka,所以在前一阵的实习面试中被问到了如何保证Kafka消息的有序性,所以本文对于Kafka这三个常被问到的问题进行总结归纳。

    可靠性

    对于消息队列而言,如果不能保证消息的可靠性可能会引起重大的生产事故。如果我们在超市里买完东西用手机进行支付,这条支付的消息存进了Kafka,但是因为某些原因导致消息丢失,商家没有收到钱,而我们却已经显示扣费,这是绝对不能容忍的,所以消息队列的可靠性至关重要。

    对于可靠性的保障,主要需要从生产者、Broker和消费者三个角度来进行实现,我们先简单介绍一下。

    Kafka是基于发布订阅模型的消息队列,主要包括生产者、Broker和消费者三个部分。生产者在Kakfa模型中负责生产消息,并将生产的消息发送到Broker中进行存储,消费者从Broker中拉取订阅的消息进行消费。如下图所示:

    从上图我们可以得知Kafka可能出现消息丢失的三个地方就是生产者、Broker和消费者。下面我们分别介绍三种情况。

    保证生产者的消息可靠性

    从本质上来说,生产者与Broker之间是通过网络进行通讯的,因此保障生产者的消息可靠性就必须要保证网络可靠性,这里Kafka使用acks=all可以设置Broker收到消息并同步到所有从节点后给生产者一个确认消息。如果生产者没有收到确认消息就会多次重复向Broker发送消息,保证在生产者与Broker之间的消息可靠性。

    保证Broker的消息可靠性

    在Broker收到了生产者的消息后,也有可能会丢失消息,最常见的情况是消息到达某个Broker后服务器就宕机了。这里需要补充说明一下Kafka的高可用性,直观的看,Kafka一般可被分成多个Broker节点,而为了增加Kafka的吞吐量,一个topic通常被分为多个partition,每个partition分布在不同的Broker上。如果一个partition丢失就会导致topic内容的部分丢失,因此partition往往需要多个副本,以此来保证高可用。

    如上图所示,一个topic被分成三个partition,每个partition又进行复制,假如此时Broker1挂了,在Broker2和Broker3上依然有完整的三个partition。此时只需要重新选举出partition的leader即可。这里还是需要强调一下,一定要将leader上的数据同步到follower上才能给生产者返回消息,否则可能造成消息丢失。

    保证消费者的消息可靠性

    这里比较容易发生消息丢失的情况是,消费者从Broker把消息拉过来,如果这个时候还没有消费完,消费者就挂了并且消费者自动提交了offset,那么此时就丢失了一条消息。所以解决办法就是关闭自动提交offset,等真正消费成功之后再手动提交offset。

    幂等性

    保证消息的幂等性,其实就是保证消息不会被重复消费。幂等性的保证需要根据具体业务具体分析,比如向MySQL插入一条订单信息,可以根据订单id查询数据库中是否已经存在该信息进行去重。如果是类似于Redis的设置key值,Redis天然支持消息的幂等性,所以这种情况下是不需要关心消息的幂等性的。总之,对于幂等性的保证完全可以根据业务需求进行具体分析。

    有序性

    消息队列在某些场景下需要严格保证消息被消费的顺序,想象一个场景,你看到你的男朋友在朋友圈晒别的女生的照片,于是你在下面评论了一句:“渣男”,然后把他的微信删了。这时候如果这两条消息的消费顺序是反过来的,也就是先删除微信,然后进行评论,那么是无法评论的。

    前面说到可靠性的时候我们提到了Kafka的topic是由多个partition组成的,那么我们可以用一种最极端的方式保证消息有序性,一个topic只设置一个partition。这里的问题就是如果一个topic只对应一个partition,那么这个吞吐量肯定就大幅下降了,这就违背了Kafka的设计初衷。

    还有一种方法是比较推荐的,由于不同partition之间是不能保证有序性的,因此保证消息在同一个partition中是保证消息有序性的关键,除了前面说的那种极端解决方案,其实还可以在发送消息时,指定一个partition,或者指定一个key,因为同一个key的消息可以保证只发送到同一个partition,这里的key一般可以用类似userid的属性来表示。在上面的场景来看就是妹子的userid先是进行了评论操作,又进行了删除好友的操作,这两个操作由于是同一个key值,因此被发往同一个partition中。

    展开全文
  • 原子A:要不全部成功,要不全部失败,比如A账户转账B账户,A-1,B+1,这二个操作要不全部成功,要不全部失败,中间不能有任何因素中断。...有序性O:执行代码,按照顺序执行。 int i = 0; boolean flag...
  • 新生儿数量的预测是医院管理中的重要问题。 依靠固有的非后效应特性,离散时间马尔可夫链(DTMC)是解决该问题的候选者。 但是经典的DTMC无法处理状态的不确定,尤其是在状态空间不是离散的情况下,这将导致不稳定...
  • 目录 1 Java内存模型(Java memory model JMM) 1.1 什么是JMM(JMM作用) 1.2 JMM组成 2 硬件内存架构与java内存模型 2.1 硬件架构 ...3.并发编程三个重要特性 3.1 原子 3.2 可见 3.3...
  • 在并发编程中有三个非常重要的特性:原子有序性,、可见,学妹发现你对它们不是很了解,她很着急,因为理解这三个特性对于能够正确地开发高并发程序有很大帮助,接下来面试中也极有可能被问到,小学妹就忍...
  • 原子、可见有序性是多线程编程中最重要的几个知识点,由于多线程情况复杂,如何让每个线程能看到正确结果,这是非常重要的,下面和千锋广州小编一起来看看吧! 原子 原子是指一个线程操作是不能被其他...
  • 我们来看一下并发编程中原子、可见有序性是怎么来。 早期CPU频率比内存频率要高很多,如果CPU每次都从内存取数据话,就会造成快车等慢车状态,严重影响CPU性能。为了解决这个问题,CPU中引入...
  • 在多线程中,volatile和synchronized都起到非常重要的作用,synchronized是通过加锁来实现线程安全。而volatile主要作用是在多处理器开发中保证共享变量对于多线程可见。 可见性的意思是,当一个线程修改...
  • 为什么要额外写一篇文章来研究...我会从并发中最重要的一些因素开始说起: 原子原子是不可分割操作。它们要么全部实现,要么全部不实现。Java中原子操作最佳例子是将一个值赋给变量。 可见可见是...
  • 我们知道,变量对于程序来说是至关重要的,如果没有变量存储和访问值,整个程序会受到限制。那么问题来了,既然程序这么需要变量,那么它到底是怎么样去存储变量和使用变量呢?存储变量这里暂且不提,到时候会有...
  • 内存是计算机中比较重要的部件(特别是对于java),它是连接cpu和硬盘桥梁(读写)。 说白了内存就是用来存放CPU临时运算数据。 内存往细了分又有:堆、栈等,任何优秀程序中都有良好内存划分 1.1、内存泄漏 ...
  • 转载于:https://www.cnblogs.com/zhujiqian/p/10811280.html
  • 在许多情况下,让计算机同时去做几件事情,不仅是因为计算机运算能力强大了,还有一个很重要的原因是计算机运算速度与它存储和通信子系统速度差距太大,大量时间都花费在磁盘I/O、网络通信或者数据库访问...
  • 并发编程算是任何语言比较难以理解部分,提到并发编程,最重要的就是线程安全问题如何解决。而线程安全问题主要体现在原子、可见以及有序这三个方面。 内容 1 CPU缓存-可见问题 可见是指当一个线程...
  • 在程序开发中,我们如何才能保证一个程序的原子是非常的重要的,保证程序原子性性,可以有效的避免在多线程过程中,出现的诡异bug。那在java程序中如果保证程序的原子呢?也就是保证当前方法在同一时刻只能有一...
  • 我们常说并发场景下有三个重要特性:原子、可见有序性。只有在满足了这三个特性,才能保证并发程序正确执行,否则就会出现各种各样问题。 原子, CAS 和 Atomic* 类,可以保证简单操作原子,对于...
  • 解决并发编程中可见有序性问题最直接方法就是禁用CPU缓存和编译器优化。但是,禁用这两者又会影响程序性能。于是我们要做是按需禁用CPU缓存和编译器优化。 如何按需禁用CPU缓存和编译器优化就需要...
  • 由于并发程序比串行程序复杂得多,其中一个重要原因是在并发程序下数据访问一致和安全将会受到严重挑战。我们需要在深入了解并行机制前提下,再定义一种规则,保证多个线程间可以有效,正确协同工作。...
  • 一端简单支撑,另一端滑动的弹性梁的形变可以用四阶常微分方程两点边值问题来描述,由于其在物理中的重要性,已有许多人研究了该类问题解的存在性,但这些文献仅限于在一般空间中讨论,并且采用的方法主要是拓扑度及...
  • 重要的 搞清楚happen-before -->...有序性:一个线程观察其他线程中指令执行顺序,由于指令 重排序存在,该观察结果一般杂乱无序。 原子:提供了互斥访问。 特性操作 可见 ...
  • 原始锯齿形边缘三角石墨烯(ZTG)是一种磁性半导体,因此其自旋极化极低。 在这里,我们报告通过功能化(包括杂原子... 我们发现表明,理想功能化ZTG结构可能有望为开发纳米级自旋电子器件带来重要的潜在应用。
  • 信息系统有序性的定义探讨(一)

    千次阅读 2007-10-12 12:29:00
    序参量在自组织系统中是一个极为重要的概念,它在系统的初始阶段并不存在, 而是在系统发展演化到一定阶段才出现的,它是一个缓慢变化的... 序参量是系统有序化的表征,如果我们说某个系统现在是有序的,那么一定可
  • 有时候,消息顺序是非常重要的,为了能顺序消费消息,我们只能启动一个consumer来消费这个queue。但是这样做问题就是,如果这个consumer宕调了话,消息就不能得到处理了,consumer可用不能得到保证。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,424
精华内容 569
关键字:

有序的重要性