• Java 并发之共享对象


    上一篇文章说的是,避免多个线程在同一时间访问对象中的同一数据,这篇文章来详细说说共享和发布对象。

    在没有同步的情况下,我们无法预料编译器、处理器安排操作执行的顺序,经常会发生以为“一定会”发生的动作实际上没有发生。可以用一些简单的方法来避免这个问题。

    在 Java 中,如果不是64位版本的,JVM 会把 double 或者 long 的读和写划分在两个 32 位中,这样一来,在多线程中,没有声明是 volatile 的 double 或者 long 也是不安全的。

    锁是同步和互斥的,同样也是内存可见的。为了避免出现读到过期的数据,读和写的线程都要使用公共的锁进行同步。

    volatile 是一种弱同步,只能保证可见性,而不能保证原子性。(为了安全,可以理解成 volatile 基本上只能使 boolean 的值原子化,像自增这种操作是不能被 volatile 原子化的)在确保 volatile 变量所引用状态的可见性、标识重要的生命周期事件(初始化或者关闭)的时候可以用,其他的时候最好不要用。下面数绵羊的代码就是一种典型的应用:

    volatile boolean asleep;
    ...
        while(!asleep) {
            count();
        }

    如果有其他的线程修改了 asleep, 让它变成 true 了,那么就会跳出循环。

    不要在构造函数中启动线程,有可能造成溢出。用单独的 start() 或者 init() 启动线程

    对于只在一个线程内使用的数据就没必要同步了,对于可以用 volatile(只有一个线程写入的变量),或者可以用栈限制:

    public in loadTheArk(Collection<Animal> cadidates) {
        SortedSet<Animal> animals;
        int numPairs = 0;
        Animal candidate = null;
        // new 了一个集合来装 cadidates,避免溢出
        animals = new TreeSet<>(new SpeciesGenderComparator());
        animals.addAll(candidates);
    
        ... // count numPairs
    }

     也可以用更规范的方法 ThreadLocal 把线程和持有数值的对象关联在一起。但是这种方法开销比较大。

    不可变的对象是线程安全的,它必须满足:

    1. 它的状态在创建后不能被修改
    2. 所有的域都是 final 的
    3. 被正确的创建(创建期间没有发生 this 引用的溢出)

    对象发布时,应该使用同步。但是对于不可变对象来说,可以不用同步。为了安全的发布对象,可以:

    • 静态初始化对象的引用
    • 把它的引用存储到 volatile 域或者 AtomicReference
    • 把它的引用存储到正确创建对象的 final 域中
    • 把它的引用存储到由正确锁保护的域中

    如果对象本身是可变的,但是发布之后状态不会被修改,那么就在发布的时候对所有线程可见,之后线程访问这个对象就不需要额外的同步了。如果后续状态会被改变,那么必须是线程安全或者锁保护的。

  • 相关阅读:
    GitHub与Markdown(学习笔记)
    “Another git process seems to be running in this repository...”Git此问题解决
    Git学习笔记--实践(三)
    Git学习笔记--配置(二)
    Java 锁(学习笔记)
    Git学习笔记--历史与安装(一)
    Java 8中Stream API(学习笔记)
    Qt Creator的下载和安装
    获取 wx.getUserInfo 接口后续将不再出现授权弹窗,请注意升级(微信小程序开发)
    大型网站架构技术一览
  • 原文地址:https://www.cnblogs.com/vivizhyy/p/3394865.html
Copyright © 2020-2023  润新知