【问题标题】:Immutable 100%, but still not thread-safe100% 不可变,但仍然不是线程安全的
【发布时间】:2018-10-17 11:29:09
【问题描述】:

我读过很多关于线程安全的文章。在我的多线程程序的某些部分,我更喜欢尝试不变性。在得到不正确的结果后,我注意到我的不可变对象不是线程安全的,尽管它是 100% 不可变的。如果我错了,请纠正我。

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() + "%)";
}
 }

啊啊啊

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

有时我得到正确的结果,但大多数时候我不是

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

这个不可变对象有什么问题?在不使用内部锁的情况下使其线程安全缺少什么?

【问题讨论】:

  • 你的班级T4 不是一成不变的。这就是您通过重新分配 g3 来改变的内容。
  • 你有什么问题??你产生了 4 个不同的不可变对象。它与线程安全无关。我很惊讶有人甚至对此表示赞同

标签: java multithreading thread-safety immutability


【解决方案1】:

都没有

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

也没有

g3 = g3.increase(1);

是线程安全的。这些复合动作不是原子的。

我建议阅读 Brian Goetz 的“Java 并发实践”:专门讨论复合动作和“发布和转义”问题的章节。

【讨论】:

  • current 是最终的,因此不可能存在竞争(最终字段在构造函数结束时被冻结)。从旧对象派生新的不可变对象是线程安全的。只是g3 = g3.increase(1); 不是,因为可能会有比赛。
【解决方案2】:

您的问题是您没有对数值变量 max 和 current 使用线程安全操作。正因为如此,许多线程可以从它们那里得到相同的值,即使它已经被改变了。

您可以添加同步块来处理对它们的读/写,但最好的方法是使用线程安全类来为您处理。

如果您需要长值,那就是 AtomicLong。看看它的文档,它有方法来做你想要的操作。

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

当您使用多线程时,您应该使用线程安全对象,例如 Atomic 系列、用于映射的 ConcurrentHashMap 等等。

希望对你有帮助!

【讨论】:

    【解决方案3】:

    这里唯一的问题是下面这行:

    g3 = g3.increase(1);
    

    这相当于以下几行:

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

    要解决此问题,您可以使用比较和交换:

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

    然后将g3 = g3.increase(1);替换为:

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

    最后,你的 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();
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2014-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-18
      • 2022-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多