用非静态成员变量初始化静态变量恰好一次
Initialize static variable with non-static member variable exactly once
我想使用 https://micrometer.io/docs/concepts#_gauges 计算 运行 个 RunInAThread 实例的数量。从文档中,最好在集合上创建一个仪表(我没有),或者使用类似 AtomicInteger 的东西。
但是,我需要一个 AtomicInteger 的静态实例,并且必须使用作为成员变量的 MeterRegistry 对其进行初始化。这样做的最佳做法是什么?我不想采用标准的单例模式,因为这意味着我总是必须调用 getInstance() 来获取 AtomicInteger 实例,并且每次都必须同步。
还有比我现在做的更好的方法吗?
public class RunInAThread implements Runnable {
private static AtomicInteger GAUGE = null;
public RunInAThread(final MeterRegistry registry) {
synchronized(this) {
if(GAUGE==null) {
GAUGE = registry.gauge("some_name", Collections.emptySet(), new AtomicInteger());
}
}
}
@Override
public void run() {
GAUGE.incrementAndGet()
doSomething();
GAUGE.decrementAndGet()
}
private void doSomething() {
// processing like a boss in a while loop
}
}
synchronized(this)
在构造函数中是完全没用的。这意味着:避免 运行 我的大括号中包含的代码与锁定同一对象的任何其他线程同时出现。你锁定的那个物体?根据定义不是任何其他线程可能拥有的东西 - 你刚刚创建*。
听起来这个MeterRegistry
概念本身就是一个单例。也许调查一下你是否可以在其他一些静态块中只初始化一次 GAUGE。但是,如果这看起来很困难或不可能,那么如果你真的想从中挤出性能,你可以使用双锁;不过,我怀疑这是否重要。同步非常快。无论如何,这在理论上应该更快:
public class RunInAThread implements Runnable {
private static final Object GAUGE_LOCK = new Object();
private static AtomicInteger GAUGE = null;
public RunInAThread(final MeterRegistry registry) {
if (GAUGE == null) {
synchronized (GAUGE_LOCK) {
if (GAUGE == null) GAUGE = registry.gauge(...);
}
}
}
}
这有几件事:
- 这个锁其实很有用。 VM 中只有一个 GAUGE_LOCK 对象,所以如果我们确实命中那个同步,它就会工作。 synchronized 还建立了 comes-before,从而保证 VM 将确保我们对 GAUGE 变量的看法得到更新;因此,没有必要让 GAUGE 变得易变。
- 如果我们很幸运并且 GAUGE 变量已更新,无论出于何种原因,我们都不会同步。
- 由于第二次 nullcheck,GAUGE 仍然不可能是 [A] null 或 [B] 一次的 return 值,我们称之为注册表。
- 这叫做'double checked locking'。两个空检查是至关重要的。
*) 您可以使构造函数中的 synchronized(this)
实际上有效,但前提是您在自己的构造函数中触发线程,或者让 this
引用从构造函数中逃逸.这些都是如此荒谬的坏事,我觉得有理由假设你不会做那些愚蠢的事情。此时我们可以简化为: synchronized(this)
在构造函数中是无用的。
您当前的同步调用与您的想法不符。在 'this' 上同步不会阻止同时实例化 RunInAThread 的两个实例,并且都将 GAUGE 检测为 null 并进行设置。
从您的代码示例中,不清楚为什么 GAUGE 必须是静态的。它来自注册表,目前尚不清楚是否可以保证两个不同的注册表对象 return 相同的 AtomicInteger。现在,如果 MeterRegistry 是单例,那么一个选项就是使用单例。类似于:
private static final AtomicInteger GAUGE = MeterRegistry.getInstances().gauge(...)
编辑
想象一下代码:
MeterRegistry reg1 = new MeterRegistry(...);
RunInAThread thread1 = RunInAThread(reg1)
MeterRegistry reg2 = new MeterRegistry(...);
RunInAThread thread2 = RunInAThread(reg1)
在那种情况下,是否真的打算在两种情况下使用相同的 GAUGE?
再想一想,之前让注册表成为单例的解决方案仍然有效。或者,改为传入原子整数并将其视为成员变量。这样会更加清晰,并且不容易出现意外行为。
MeterRegistery reg = new MeterRegistry(...);
AtomicInteger gauge = reg.gauge(...);
RunInAThread thread1 = RunInAThread(gauge);
RunInAThread thread2 = RunInAThread(gauge);
我认为关键是你正在努力解决这个低于你应该达到的水平。
我想使用 https://micrometer.io/docs/concepts#_gauges 计算 运行 个 RunInAThread 实例的数量。从文档中,最好在集合上创建一个仪表(我没有),或者使用类似 AtomicInteger 的东西。
但是,我需要一个 AtomicInteger 的静态实例,并且必须使用作为成员变量的 MeterRegistry 对其进行初始化。这样做的最佳做法是什么?我不想采用标准的单例模式,因为这意味着我总是必须调用 getInstance() 来获取 AtomicInteger 实例,并且每次都必须同步。
还有比我现在做的更好的方法吗?
public class RunInAThread implements Runnable {
private static AtomicInteger GAUGE = null;
public RunInAThread(final MeterRegistry registry) {
synchronized(this) {
if(GAUGE==null) {
GAUGE = registry.gauge("some_name", Collections.emptySet(), new AtomicInteger());
}
}
}
@Override
public void run() {
GAUGE.incrementAndGet()
doSomething();
GAUGE.decrementAndGet()
}
private void doSomething() {
// processing like a boss in a while loop
}
}
synchronized(this)
在构造函数中是完全没用的。这意味着:避免 运行 我的大括号中包含的代码与锁定同一对象的任何其他线程同时出现。你锁定的那个物体?根据定义不是任何其他线程可能拥有的东西 - 你刚刚创建*。
听起来这个MeterRegistry
概念本身就是一个单例。也许调查一下你是否可以在其他一些静态块中只初始化一次 GAUGE。但是,如果这看起来很困难或不可能,那么如果你真的想从中挤出性能,你可以使用双锁;不过,我怀疑这是否重要。同步非常快。无论如何,这在理论上应该更快:
public class RunInAThread implements Runnable {
private static final Object GAUGE_LOCK = new Object();
private static AtomicInteger GAUGE = null;
public RunInAThread(final MeterRegistry registry) {
if (GAUGE == null) {
synchronized (GAUGE_LOCK) {
if (GAUGE == null) GAUGE = registry.gauge(...);
}
}
}
}
这有几件事:
- 这个锁其实很有用。 VM 中只有一个 GAUGE_LOCK 对象,所以如果我们确实命中那个同步,它就会工作。 synchronized 还建立了 comes-before,从而保证 VM 将确保我们对 GAUGE 变量的看法得到更新;因此,没有必要让 GAUGE 变得易变。
- 如果我们很幸运并且 GAUGE 变量已更新,无论出于何种原因,我们都不会同步。
- 由于第二次 nullcheck,GAUGE 仍然不可能是 [A] null 或 [B] 一次的 return 值,我们称之为注册表。
- 这叫做'double checked locking'。两个空检查是至关重要的。
*) 您可以使构造函数中的 synchronized(this)
实际上有效,但前提是您在自己的构造函数中触发线程,或者让 this
引用从构造函数中逃逸.这些都是如此荒谬的坏事,我觉得有理由假设你不会做那些愚蠢的事情。此时我们可以简化为: synchronized(this)
在构造函数中是无用的。
您当前的同步调用与您的想法不符。在 'this' 上同步不会阻止同时实例化 RunInAThread 的两个实例,并且都将 GAUGE 检测为 null 并进行设置。
从您的代码示例中,不清楚为什么 GAUGE 必须是静态的。它来自注册表,目前尚不清楚是否可以保证两个不同的注册表对象 return 相同的 AtomicInteger。现在,如果 MeterRegistry 是单例,那么一个选项就是使用单例。类似于:
private static final AtomicInteger GAUGE = MeterRegistry.getInstances().gauge(...)
编辑 想象一下代码:
MeterRegistry reg1 = new MeterRegistry(...);
RunInAThread thread1 = RunInAThread(reg1)
MeterRegistry reg2 = new MeterRegistry(...);
RunInAThread thread2 = RunInAThread(reg1)
在那种情况下,是否真的打算在两种情况下使用相同的 GAUGE?
再想一想,之前让注册表成为单例的解决方案仍然有效。或者,改为传入原子整数并将其视为成员变量。这样会更加清晰,并且不容易出现意外行为。
MeterRegistery reg = new MeterRegistry(...);
AtomicInteger gauge = reg.gauge(...);
RunInAThread thread1 = RunInAThread(gauge);
RunInAThread thread2 = RunInAThread(gauge);
我认为关键是你正在努力解决这个低于你应该达到的水平。