简体   繁体   English

100% 不可变,但仍然不是线程安全的

[英]Immutable 100%, but still not thread-safe

I've read a lot about thread-safety.我读过很多关于线程安全的文章。 In certain part of my multi-threaded program, I preferred to try the immutability.在我的多线程程序的某些部分,我更喜欢尝试不变性。 After getting incorrect results, I noticed my immutable object is not thread-safe although it is 100% immutable.在得到不正确的结果后,我注意到我的不可变对象不是线程安全的,尽管它是 100% 不可变的。 Please correct me if I'm wrong.如果我错了,请纠正我。

public final class ImmutableGaugeV4 {
private final long max, current;

public ImmutableGaugeV4(final long max) {
    this(max, 0);
}

private ImmutableGaugeV4(final long max, final long current) {
    this.max = max;
    this.current = current;
}

public final ImmutableGaugeV4 increase(final long increament) {
    final long c = current;
    return new ImmutableGaugeV4(max, c + increament);
}

public final long getCurrent() {
    return current;
}

public final long getPerc() {
    return current * 100 / max;
}

@Override
public final String toString() {
    return "ImmutableGaugeV4 [max=" + max + ", current=" + current + "](" + getPerc() + "%)";
}
 }

aaaaa啊啊啊

public class T4 {
public static void main(String[] args) {
    new T4().x();
}

ImmutableGaugeV4 g3 = new ImmutableGaugeV4(10000);

private void x() {
    for (int i = 0; i < 10; i++) {
        new Thread() {
            public void run() {
                for (int j = 0; j < 1000; j++) {
                    g3 = g3.increase(1);
                    System.out.println(g3);
                }
            }

        }.start();
    }
}
}

Sometimes I'm getting correct results, and most of the times I'm not有时我得到正确的结果,而大多数时候我没有

ImmutableGaugeV4 [max=10000, current=9994](99%)
ImmutableGaugeV4 [max=10000, current=9995](99%)
ImmutableGaugeV4 [max=10000, current=9996](99%)
ImmutableGaugeV4 [max=10000, current=9997](99%)

What is wrong with this immutable object?这个不可变对象有什么问题? What is missing to make it thread-safe without using intrinsic locks?在不使用内在锁的情况下使它成为线程安全缺少什么?

Neither 也不

final long c = current;
return new ImmutableGaugeV4(max, c + increament);

nor 也不

g3 = g3.increase(1);

is thread-safe. 是线程安全的。 These compound actions aren't atomic. 这些复合动作不是原子的。

I recommend reading "Java concurrency in practice" by Brian Goetz: the chapters devoted to compound actions and "publication and escape" problems. 我建议阅读Brian Goetz的“实践中的Java并发性”:这些章节专门讨论复合操作以及“发布和转义”问题。

Your problem is that you are not using thread safe operations for your numeric variables max and current. 您的问题是您没有对数字变量max和current使用线程安全操作。 Because of that, many threads can get the same value from them even tough it has already been changed. 因此,即使已经更改,许多线程也可以从中获得相同的值。

You could add synchronized blocks to handle reading / writing to them, but the best approach is to use thread safe classes to handle that for you. 您可以添加同步块来处理对其的读/写,但是最好的方法是使用线程安全类为您处理。

If you need long values, that would be AtomicLong. 如果需要长值,则为AtomicLong。 Take a look at it's documentation, it has methods to do the operations you want. 看一下它的文档,它具有执行所需操作的方法。

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicLong.html https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicLong.html

Whenever you're multithreading you should go for threadsafe objects, such as the Atomic family, ConcurrentHashMap for maps, and so on. 每当使用多线程时,都应该使用线程安全的对象,例如Atomic系列,用于映射的ConcurrentHashMap等。

Hope it helps! 希望能帮助到你!

The only problem here is the following line:这里唯一的问题是以下行:

g3 = g3.increase(1);

This is equivalent to the following lines:这等效于以下行:

var tmp = g3;
tmp = tmp.increase(1);
g3 = tmp;

To fix this, you could use a Compare And Swap:要解决此问题,您可以使用比较和交换:

private static final VarHandle G3;
static {
    try {
        G3 = MethodHandles.lookup().findVarHandle(T4.class, "g3", ImmutableGaugeV4.class);
    } catch (ReflectiveOperationException roe) {
        throw new Error(roe);
    }
}

And then replace g3 = g3.increase(1);然后替换g3 = g3.increase(1); with:和:

ImmutableGaugeV4 oldVal, newVal;
do {
    oldVal = g3;
    newVal = oldVal.increase(1);
} while (!G3.compareAndSet(T4.this, oldVal, newVal));
System.out.println(newVal);

In the end, your T4 becomes:最后,你的 T4 变成:

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class T4 {
    public static void main(String[] args) {
        new T4().x();
    }
    
    ImmutableGaugeV4 g3 = new ImmutableGaugeV4(10000);
    private static final VarHandle G3;
    static {
        try {
            G3 = MethodHandles.lookup().findVarHandle(T4.class, "g3", ImmutableGaugeV4.class);
        } catch (ReflectiveOperationException roe) {
            throw new Error(roe);
        }
    }
    
    private void x() {
        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        ImmutableGaugeV4 oldVal, newVal;
                        do {
                            oldVal = g3;
                            newVal = oldVal.increase(1);
                        } while (!G3.compareAndSet(T4.this, oldVal, newVal));
                        System.out.println(newVal);
                    }
                }
                
            }.start();
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM