我正在尝试使应用程序移植到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
的新实例为静态,那么访问该实例的线程的含义是什么?使该实例静态化是我使其工作的唯一方法。
这是错误的解决方法吗?是否有一种更简便的方法以线程安全的方式(除了函数自变量和返回值)跨不同类检索值?
答案 0 :(得分:2)
如果我在Epi中声明一个
Eparams
的新实例为静态,那么访问该实例的线程的含义是什么?使该实例静态化是我使其工作的唯一方法。
static
不会以任何方式使成员成为线程安全的,只是通过类名简化了对其的访问。
这是错误的解决方法吗?
是的,现在对a
的访问是绝对不安全的。
我在这里建议的选项:
使用synchronized
访问者。
在AtomicLong
和Double.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
的值。
可以通过在synchronized
和getter
上使用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();
}
}