【问题标题】:Thread Synchronization on Integer instance variable整数实例变量上的线程同步
【发布时间】:2016-06-30 08:21:31
【问题描述】:
public class Main{
    public static void main(String[] args) throws Exception {
        // Creating objects for class Check(2 different objects)
        Check c = new Check("s1");
        Check c1 = new Check("s2");
        c.start();c1.start();
   }
}
class Check extends Thread{
    Check(String name){super(name);}
    private Integer ab = 2;
    public void run(){
        synchronized (ab) {
            System.out.println(Thread.currentThread().getName());
            for(int i=0;i<10;i++)System.out.print(i+" ");
        }
    }
}

这里我已经对变量 ab 进行了同步。而且我还创建了两个不同的类 Check 实例,但我总是得到 s1 的输出,然后是 s2,反之亦然,但没有混合,为什么会这样?当我已经创建了两个单独的对象(在 main 中),所以两个不同的线程,两个不同的 ab 变量,那么它如何成为两个不同对象的共享资源?

【问题讨论】:

  • 顺便说一句,您没有在 变量 上进行同步,而是在变量所引用的对象上进行同步。这是一个重要的区别,因为有些人犯了错误,没有意识到变量可以在不同的时间引用不同的对象,有些人犯了错误,没有意识到同一个对象可以被多个变量引用。
  • 这段代码看起来更像是一个测试代码而不是一个真实的案例,但是如果你想在一个无锁但线程安全的Number上同步,你可以使用AtomicInteger .
  • @jameslarge:我明白你在说什么。我对此并不精确,因为我的问题集中在其他一点上。无论如何,谢谢。 :)
  • @Jean-FrançoisSavard:是的,它是一个测试代码,与实际项目无关。只是检查。是的,AtomicInteger,我知道。谢谢你。 :)
  • @Jean-François:澄清一下,atomicinteger 更像是锁定的替代品,它不应该用作锁定。

标签: java multithreading thread-safety


【解决方案1】:

TL;DR - 这是因为 Integer 池化。将 ab 设为Object(即Object ab = new Object())以保证Check 的每个锁定实例都不会干扰其他实例。


我最初也很困惑。有趣的是,如果你改变了

private Integer ab = 2;

到

private Object ab = new Object();

同步消失(每次运行都会得到不同的输出)。回到 ab 作为Integer,我在调试模式下运行您的代码(在打印线程名称行上有一个断点)并发现以下内容。这是第一个线程:

这是第二个线程。

请注意,它实际上是同一个对象,Integer@443。即使你认为你得到了两个不同的对象,两个 Check 实例中的 ab 字段都引用了内存中的同一个对象!因此,是的,有正确的同步。那么,问题是,说两次Integer ab = 2 如何让你在内存中获得相同的Integer 对象。


通过说Integer ab = 2,您正在使用自动装箱,通过它,原始值(int 类型)会自动转换为相应的对象类型Integer。这相当于调用自动装箱方法调用:

private Integer ab = Integer.valueOf(2);

如果我们查看Integer.valueOf,我们会注意到它有一个特定范围内的值池:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

对于大多数常规设置,这将包括值2。因此,当您调用此方法时,您将在内存中获得相同的 Integer 值。

【讨论】:

  • 真的很有趣。非常感谢。 :)
  • “一定范围”默认是-128到127。请注意,可以更改 IntegerCache.high。 IntegerCache.low 不能。
【解决方案2】:

这是由于 java 中的 Ingeter Pool 概念而发生的。

介于 -128 和 127(含)之间的整数,使用方式与字符串池相同。

因此,当您使用private Integer ab = 2; 时,Check 的两个对象共享 ab。

您可以使用 value > 128 或任何其他类型的对象,这样您的代码就不会被同步。

您可以在这里查看答案:Why does the behavior of the Integer constant pool change at 127? 以了解整数池概念。

【讨论】:

  • 如果您需要明确不同的对象,您应该明确创建它们。在当前的实现中,使用更大的整数会给你不同的对象,但不能保证这样做。
【解决方案3】:

如果您查看字节码,您可能会看到以下代码:

LINENUMBER 15 L1
ALOAD 0
ICONST_2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
PUTFIELD com/example/Check.ab : Ljava/lang/Integer;

Java 尝试通过调用 Integer 类的 #valueOf 将原始值 2 装箱到对象。正如我们所知,Integer 的 valueOf 实现了 Flyweight 模式。因此,实例之间共享同一个对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-10
    • 1970-01-01
    • 1970-01-01
    • 2011-01-08
    相关资源
    最近更新 更多