Java 整数对象用作同步块中的锁时可能出现并发问题

Java 整数对象用作同步块中的锁时可能出现并发问题,java,multithreading,Java,Multithreading,在处理多线程程序时,我观察到了非常奇怪的行为 当Integer对象用作锁时,似乎可以在同步块中使用多个线程。 甚至认为这是不可能的 如果我在下面的程序中使用任何其他静态成员,如's'、'o'和'c'define,它将按预期工作 代码- 正如您在打印“101112”时看到的,有两个线程在同步块中执行 是我做错了什么还是我错过了什么 这与幕后的一些优化有关吗? 因为如果我用“ii”来锁定,一切都会完美工作。 使用“i”时,也会打印“fail”,但很少打印。 下面是用于运行该程序的java版本。jav

在处理多线程程序时,我观察到了非常奇怪的行为

当Integer对象用作锁时,似乎可以在同步块中使用多个线程。 甚至认为这是不可能的

如果我在下面的程序中使用任何其他静态成员,如's'、'o'和'c'define,它将按预期工作

代码-

正如您在打印“101112”时看到的,有两个线程在同步块中执行

是我做错了什么还是我错过了什么

这与幕后的一些优化有关吗? 因为如果我用“ii”来锁定,一切都会完美工作。

使用“i”时,也会打印“fail”,但很少打印。

下面是用于运行该程序的java版本。java版本java版本“1.7.0_51”java(TM)SE运行时环境(构建 1.7.0_51-b13)Java热点(TM)客户端虚拟机(构建24.51-b03,混合模式,共享)

您可以使用下面的程序运行程序来运行这一系列的时间,并查看结果

public class MyThread extends Thread{
    private static Integer ii=1;
    private static Integer i=1;
    private static String s="1";
    private static Object o= new Object();
    private static Class<MyThread> c= MyThread.class;

    public void run(){
        synchronized(ii){
            System.out.print(i++ +" ");
            System.out.print(i+"   ");
        }
    }
    public static void main(String[] str) throws InterruptedException{
        for(int j=0;j<100;j++){
            for(int i=0;i<100;i++){
                MyThread t= new MyThread();
                t.start();
            }
            Thread.sleep(50);
            System.out.println();
            MyThread t= new MyThread();
            t.start();t.join();
            if(i!=102)
                System.out.println("fail");
            Thread.sleep(50);
            i=1;
            System.out.println();
        }
    }
}
公共类MyThread扩展线程{
私有静态整数ii=1;
私有静态整数i=1;
私有静态字符串s=“1”;
私有静态对象o=新对象();
私有静态类c=MyThread.Class;
公开募捐{
同步(二){
系统输出打印(i+++);
系统输出打印(i+“”);
}
}
公共静态void main(字符串[]str)引发InterruptedException{
对于(intj=0;j

相当于

i = Integer.valueOf(i.intValue() + 1)
换句话说,
i
现在引用的对象不同于您最初同步的对象

如果线程在
i
发生更改后碰巧到达
synchronized
块,它也会进入,因为它在不同的对象上获取监视器

因为如果我用“ii”来锁定,一切都会很完美


您没有在任何地方更改对
ii
的引用。

我认为问题在于您通过在同步块内执行
I++
来更改“I”的值,这导致分配给
I
的对象每次都不同(在Eclipse中进行检查,查看增量操作前后变量的对象id).

是一个不可变的类。当您对它执行类似于
++
的操作时,您正在创建一个新实例。这可能会导致线程在不同的对象上同步,使它们能够同时访问
已同步的
块。

这正是锁应该声明为
final的原因。这是一个例子,说明了你可以如何射中自己的脚。

在Integer上同步似乎是个坏主意:这是我不喜欢自动装箱的原因之一。尽管在
Integer上同步是一件危险的事,因为类
Integer
所做的实例缓存可能会产生意外的影响。我也是这样做的t意思是如果我们锁定
String
对象,并对
synchronized
块中的
String
对象进行一些处理,使其指向其他字符串对象,我们将获得相同的行为?@sakura
synchronized
通过获取变量(或表达式)引用的对象上的监视器来工作。如果您更改该变量作为引用的内容,并且不同的线程到达该变量上的同步块,则它会从不同的对象获取锁。从这个意义上说,是的。
public class MyThread extends Thread{
    private static Integer ii=1;
    private static Integer i=1;
    private static String s="1";
    private static Object o= new Object();
    private static Class<MyThread> c= MyThread.class;

    public void run(){
        synchronized(ii){
            System.out.print(i++ +" ");
            System.out.print(i+"   ");
        }
    }
    public static void main(String[] str) throws InterruptedException{
        for(int j=0;j<100;j++){
            for(int i=0;i<100;i++){
                MyThread t= new MyThread();
                t.start();
            }
            Thread.sleep(50);
            System.out.println();
            MyThread t= new MyThread();
            t.start();t.join();
            if(i!=102)
                System.out.println("fail");
            Thread.sleep(50);
            i=1;
            System.out.println();
        }
    }
}
i++
i = Integer.valueOf(i.intValue() + 1)