对变量的线程安全访问

时间:2018-07-26 12:08:34

标签: java multithreading thread-safety

我正在尝试使应用程序移植到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;
    }
}

我想在另一个类a中访问类型Eparams的{​​{1}}的值:

EpiParts

我需要public class EpiParts { public void test() { Epi epi = new Epi(); Eparams eparams = epi.getEparams(); double val= eparams.getA(); System.out.print(val); } } 值是非静态的,并且有多个线程访问此类。是我做到的最好方法吗?

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

这是错误的解决方法吗?是否有一种更简便的方法以线程安全的方式(除了函数自变量和返回值)跨不同类检索值?

4 个答案:

答案 0 :(得分:2)

  

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

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

  

这是错误的解决方法吗?

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

我在这里建议的选项:

  1. 使用synchronized访问者。

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

  

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

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

答案 1 :(得分:1)

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

您唯一的问题是访问a的多个线程可能会看到过时的值。最简单的答案是使a 易变

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);
    }
}

答案 2 :(得分:0)

带有static Eparams eparams的部分不应起重要作用。线程彼此之间进行交互的唯一方法是访问Eparams本身的成员,例如设置a的值。

可以通过在synchronizedgetter上使用setter关键字来轻松解决此问题:

public synchronized double getA() {
    return a;
}

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

可以在其他question中找到关于synchronized的更多信息。

答案 3 :(得分:0)

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的引用分享吗?这样做意味着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();
    }
}