用非静态成员变量仅初始化一次静态变量。

huangapple go评论104阅读模式
英文:

Initialize static variable with non-static member variable exactly once

问题

我想要使用 https://micrometer.io/docs/concepts#_gauges 来计算正在运行的 RunInAThread 实例的数量。根据文档,最好的做法要么在集合上创建一个 gauge(但我没有集合),要么使用像 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() {   
       // 在 while 循环中进行处理   
    }

}
英文:

I want to count the number of running RunInAThread instances using https://micrometer.io/docs/concepts#_gauges. From the docs, it is best to either create a gauge on a collection (which I dont have), or use something like AtomicInteger.

However, I need a static instance of an AtomicInteger, and it must be initialized using the MeterRegistry which is a member variable. What is the best practice to do this? I don't want to go for a standard singleton pattern as this would mean I always have to call getInstance() to get the AtomicInteger instance and this would have to be synchronized every time.

Is there any better way than I'm doing right now?

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   
    }

}

答案1

得分: 3

synchronized(this) 在构造函数中是完全无用的。这意味着:避免与锁定在相同对象上的任何其他线程同时运行括号中的代码。而你正在锁定的对象?从定义上看,不可能是任何其他线程可能拥有的东西 - 你刚刚被创建*。

听起来,这个 MeterRegistry 概念本身就是一个单例。也许可以调查一下,是否可以在其他静态块中初始化那个 GAUGE 一次。但是,如果这似乎很困难或不可能,那么如果你真的想从中挤取性能,可以使用双重锁定;虽然我怀疑这没什么关系,因为 synchronized 很快。不管怎样,从理论上讲,这应该会更快:

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

这个代码实现了以下几点:

  1. 这个锁实际上是有用的。在虚拟机中只有一个 GAUGE_LOCK 对象,所以如果我们真的遇到了那个 synchronized,它会起作用。synchronized 还建立了 happens-before 关系,从而确保虚拟机会确保我们对 GAUGE 变量的视图已更新;因此,没有必要将 GAUGE 声明为 volatile
  2. 如果我们幸运,GAUGE 变量已经由于某种原因被更新,那么我们就不需要同步。
  3. 由于第二个空检查,GAUGE 永远只能是 null 或者我们调用 registry 一次返回的值。
  4. 这被称为 "双重检查锁定"。这两次空检查至关重要。

*) 你可以让构造函数中的 synchronized(this) 产生实际效果,但前提是你要在自己的构造函数内部启动一个线程,或者让你的 this 引用逃逸出构造函数。这两种做法都是如此荒谬,以至于我认为你不会做这么愚蠢的事情。在这种情况下,我们可以简化为:构造函数中的 synchronized(this) 是无用的。

英文:

synchronized(this) in a constructor is completely useless. that means: Avoid running the code contained in my braces simultaneously with any other thread that locks on the same object. And that object you are locking on? Is by definition not something any other thread could possibly have - you were just created*.

It sounds like this MeterRegistry concept is itself a singleton. Maybe investigate if you can just init that GAUGE once during some other static block. But, if that seems difficult or impossible, then you could use double locking if you really want to squeeze performance out of it; I doubt it matters, though. synchronized is pretty fast. Anyway, this should be even faster in theory:

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

This does a few things:

  1. The lock is actually useful. There is only one GAUGE_LOCK object in the VM, so if we do hit that synchronized, it'll work. The synchronized also establishes comes-before, thus guaranteeing that the VM will ensure our view of the GAUGE variable is updated; therefore, there is no need to make GAUGE volatile.
  2. If we are lucky and the GAUGE variable has been updated, for any reason, then we never synchronize.
  3. It is still, due to the second nullcheck, impossible for GAUGE to ever be anything other than either [A] null, or [B] the return value of the one time, ever, we call registry.
  4. This is called 'double checked locking'. The two null checks are crucial.

*) You can make synchronized(this) in a constructor actually have an effect, but only if you either fire off a thread within your own constructor, or let your this ref escape from your constructor. These are both such preposterously bad things to do, that I feel justified in assuming you won't do something that daft. At which point we can simplify to: synchronized(this) in a constructor is useless.

答案2

得分: 0

你当前的同步调用并没有达到你预期的效果。在'this'上进行同步操作并不能防止同时实例化两个RunInAThread实例,并且两者都会检测到GAUGE为null并进行设置。

从你的代码示例中,并不清楚为什么GAUGE必须是静态的。它来自registry,而并没有明确保证两个不同的registry对象会返回相同的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吗?

再仔细思考一下之前的解决方案,使registry成为单例仍然是可行的。或者,传入这个原子整数并将其视为成员变量。这样会更加清晰,不容易出现意外行为。

MeterRegistry reg = new MeterRegistry(...);
AtomicInteger gauge = reg.gauge(...);
RunInAThread thread1 = RunInAThread(gauge);
RunInAThread thread2 = RunInAThread(gauge);

我认为关键是你试图在比应该的层次更低的层次解决这个问题。

英文:

Your current synchronized call isn't doing what you think. Synchronizing on 'this' won't prevent two instances of RunInAThread from being instantiated at the same time and both detecting GAUGE as null and setting it.

From your code example it isn't clear why GAUGE has to be static. It is coming from registry and it isn't clear that there are any guarantees that two different registry objects will return the same AtomicInteger. Now if MeterRegistry is a singleton than one option would be use the singleton. Something like:

private static final AtomicInteger GAUGE = MeterRegistry.getInstances().gauge(...)

edit
Imagine the code:

MeterRegistry reg1 = new MeterRegistry(...);
RunInAThread thread1 = RunInAThread(reg1)

MeterRegistry reg2 = new MeterRegistry(...);
RunInAThread thread2 = RunInAThread(reg1)

In that case is it really the intention that the same GAUGE would be used in both cases?

Thinking about this some more the previous solution of making the registry a singleton still stands. Alternatively, instead pass in the atomic integer and treat it as a member variable. That would be much more clear and not prone to unexpected behavior.

MeterRegistery reg = new MeterRegistry(...);
AtomicInteger gauge = reg.gauge(...);
RunInAThread thread1 = RunInAThread(gauge);
RunInAThread thread2 = RunInAThread(gauge);

I think that the key is that you are trying to solve this a level below where you should be.

huangapple
  • 本文由 发表于 2020年8月20日 23:33:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/63508484.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定