我知道这是旧的,但我发现没有完美的方法来实现这一点,更具体地说是:
试图找到一种不易出错的方法来保护“一次执行(任何)...”中的代码
我将补充说“同时尊重先发生的行为”。这是在您的情况下实例化单例所必需的。
IMO 实现这一目标的最佳方法是通过同步函数:
public<T> T transaction(Function<NonSyncObject, T> transaction) {
synchronized (lock) {
return transaction.apply(nonSyncObject);
}
}
这允许在给定对象上执行原子“事务”。
其他选项是双重检查自旋锁:
for (;;) {
T t = atomicT.get();
T newT = new T();
if (atomicT.compareAndSet(t, newT)) return;
}
在这个new T();会被重复执行,直到值设置成功,所以它并不是真正的“一次做某事”。
这仅适用于写入事务的复制,并且可以通过调整代码来帮助“实例化对象一次”(实际上是实例化了许多对象,但最终引用了相同的对象)。
最后一个选项是第一个选项的性能最差的版本,但这个选项确实发生在 AND ONCE 之前(与双重检查自旋锁相反):
public void doSomething(Runnable r) {
while (!atomicBoolean.compareAndSet(false, true)) {}
// Do some heavy stuff ONCE
r.run();
atomicBoolean.set(false);
}
第一个是更好的选择的原因是它正在做这个做的事情,但是以更优化的方式。
附带说明一下,在我的项目中,我实际上使用了下面的代码(类似于@the8472 的答案),当时我认为是安全的,它可能是:
public T get() {
T res = ref.get();
if (res == null) {
res = builder.get();
if (ref.compareAndSet(null, res))
return res;
else
return ref.get();
} else {
return res;
}
}
关于这段代码的事情是,作为写时复制循环,这个生成多个实例,每个竞争线程一个,但只有一个被缓存,第一个,所有其他构造最终都会被 GC'd。
看看 putIfAbsent 方法,我发现它的好处是跳过了 17 行代码,然后是一个同步体:
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
然后同步体本身就是另外34行:
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
使用 ConcurrentHashMap 的优点是它无疑可以工作。