【问题标题】:Threadsafe access to variables线程安全访问变量
【发布时间】:2019-01-03 10:20:19
【问题描述】:

我正在尝试将应用程序移植到 Java 线程安全,因此希望尽量减少所需的静态变量数量。

我将展示一个我想要澄清的问题的示例。我有一个类Eparams如下:

public class Eparams {
    private double a;

    public double getA() {
        return a;
    }
    public void setA(double a) {
        this.a = a;
    }
}

我在另一个类中设置了值,Epi

public class Epi {

    static Eparams eparams = new Eparams();

    public void epi() {
        eparams.setA(3.44);
    }

    public Eparams getEparams() {
        return eparams;
    }
}

我想在另一个类EpiParts 中访问a 类型的a 的值:

public class EpiParts {

    public void test() {
        Epi epi = new Epi();

        Eparams eparams = epi.getEparams();

        double val= eparams.getA();
        System.out.print(val);
    }
}

我需要Eparams 值是非静态的,并且有多个线程访问这个类。我所做的方式是实现这一目标的最佳方式吗?

如果我在Epi 中声明Eparams 的一个新实例是静态的,那么访问这个实例的线程有什么含义?使这个实例静态是我让它工作的唯一方法。

这是错误的处理方式吗?有没有更简单的方法以线程安全的方式跨不同类检索值(除了函数参数和返回值)?

【问题讨论】:

  • 你可能想同步访问a
  • 您可能想看看synchronization。您可以提供同步方法来访问(getter)或更改(setter)实例变量

标签: java multithreading thread-safety


【解决方案1】:

如果我在 Epi 中将 Eparams 的新实例声明为静态的,那么访问此实例的线程有什么含义?使这个实例静态是我让它工作的唯一方法。

static 不会以任何方式使成员成为线程安全的,只是通过类名简化对它的访问。

这是错误的处理方式吗?

是的,现在对a 的访问根本不安全。

我在这里建议的选项:

  1. 使用synchronized 访问器。

  2. 使用AtomicLong 变量分别对getter 和setter 进行Double.longBitsToDouble(long)Double.doubleToLongBits(double) 转换。

有没有更简单的方法...?

考虑第一种方法,简单而严格。

【讨论】:

    【解决方案2】:

    您拥有的结构并不是真正的线程不安全,因为它实际上不会在多线程/重负载下崩溃或做非常奇怪的事情。

    您遇到的唯一问题是访问a 的多个线程可能会看到陈旧的值。最简单的答案是使a volatile

    public class Eparams {
        // Volatile to ensure `happens before`.
        private volatile double a;
    
        public double getA() {
            return a;
        }
        public void setA(double a) {
            this.a = a;
        }
    }
    
    public class Epi {
        // No escape will happen here.
        Eparams eparams = new Eparams();
    
        // Note that this is never called - is that deliberate or should this be a constructor?
        public void epi() {
            eparams.setA(3.44);
        }
    
        public Eparams getEparams() {
            return eparams;
        }
    }
    
    public class EpiParts {
    
        public void test() {
            Epi epi = new Epi();
    
            Eparams eparams = epi.getEparams();
    
            double val= eparams.getA();
            System.out.print(val);
        }
    }
    

    【讨论】:

    • 感谢您的反馈。 epi() 直接从 main() 调用(对于这个演示示例)
    【解决方案3】:

    static Eparams eparams 的部分不应该起很大的作用。线程可以相互交互的唯一方式是访问Eparams 本身的成员,例如设置a的值。

    这个问题可以通过在gettersetter 上使用synchronized 关键字轻松解决:

    public synchronized double getA() {
        return a;
    }
    
    public synchronized void setA(double a) {
        this.a = a;
    }
    

    更多关于synchronized的信息可以在另一个question中找到。

    【讨论】:

    • 是的,我考虑过添加同步关键字,但这不适合我正在构建的应用程序。多个线程需要访问,使 getter/setter 同步会增加应用程序的时间延迟。
    • @arsenal88 这是唯一的方法。如果你想要一致的数据,你必须在某个地方同步,否则你会很快遇到冲突和不一致
    • @Lino 同步不是线程安全的唯一方法,最终值和副本是其他一些示例。 @arsenal88 请注意,synchronized 不会自动使您的代码成为线程安全的,它只是限制其他线程同时进入同步块。例如。如果某些内容读取Eparams.a,然后另一个线程立即将Eparams.a 设置为新值,那么第一次读取的值是否仍然有效?
    • @xtratic 当然,但在来自 OP 的示例中 final 值没有意义,这就是为什么应该使用 synchronized 访问。但我同意immutability 可能是大多数时候首选
    • 我只看到 Eparams 被设置为文字值 eparams.setA(3.44); 所以 final 可能适用于 OP。至于同步,在这种情况下它实际上提供了什么好处?分别在每个方法上同步意味着一个可以在另一个正在写入的同时读取。如果这就是您要处理的方式,为什么还要同步它们?
    【解决方案4】:
    public class Epi {
        static Eparams eparams = new Eparams();
    

    Eparams 是静态的意味着所有Epi 实例都引用相同的Epi。因此,如果Epi 实例在不同的线程中运行并更新Eparams.setA(),那么您将遇到问题。

    我在多线程时的建议是尽可能使用最终变量。如果Eparams 是不可变的,那么您将是线程安全的。 Eparams 中的值是否真的需要从它的初始值改变?如果没有,那么我会做这样的事情。

    public class Eparams {
        final double a;
        public Eparams(double a){
            this.a = a;
        }
        public double getA(){
            return a;
        }
    }
    

    当您确实需要一些可变的并在线程之间共享时,请确保它是同步或原子更新的,并记录该值可能被多个线程修改并且可能随时更改。

    还要确定您在界面中公开的内容:对于public Eparams getEparams(),您是否真的要为EpiParts 提供对Eparams 的引用,Epi 实例正在共享该Eparams?这样做意味着EpiParts 现在可以调用Eparams.setA() 并开始修改值。

    我会选择以下内容。希望它接近您的目标:

    // immutable is thread-safe
    // make all fields final to make `Eparams` immutable
    public class Eparams {
        private final double a;
        public Eparams(double a){ this.a = a; }
        public double getA(){
            return a;
        }
    }
    
    public class Epi {
        // shared between all instances of `Epi` but is immutable
        // also final so that nothing swaps out a new `Eparams` unexpectedly
        // which would be thread-unsafe
        private static final Eparams eparams = new Eparams(3.44);
    
        // it is safe to give strangers a reference to your immutable field
        /** Eparams is a constant */
        public static getEparams() {
            return eparams;
        }
    
        // if `Eparams` were mutable then be careful about giving 
        // it to strangers and instead do something like this
        /** Eparams is mutable and shared between `Epi` instances
            it's values may change at any moment, be careful.. */
        public static getEparamsA() {
            return eparams.getA();
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多