原因:Synchronized锁的是对象,也就是identityHashCode所指向的内存地址中的对象实例(根据对象内存地址生成散列值)
下面先看一个demo
static public class MyRunable implements Runnable{
private Integer ncount = 0;
public void run() {
Integer temp = 0;
//System.out.println("thread id:"+Thread.currentThread().getId()+"count"+ncount+" identityHashCode:"+System.identityHashCode(ncount)+" HashCode:"+ncount.hashCode());
while (temp <= 100){
synchronized (ncount){
ncount++;
temp = ncount;
//System.out.println("thread id:"+Thread.currentThread().getId()+"count"+ncount+" identityHashCode:"+System.identityHashCode(ncount)+" HashCode:"+ncount.hashCode());
System.out.println("thread id:"+Thread.currentThread().getId()+"count:"+ncount);
}
try {
Thread.sleep(1000000000);
}
catch (Exception e){
e.printStackTrace();
}
}
}
}
public static void main(String [] args) throws Exception{
Runnable runnable = new MyRunable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
结果输出(如果结果不一致多运行几次):
发现结果和我们期望不一样,count的值没被锁住。这是为什么呢?难道是成员变量的原因?我们将ncount修改成static
static private Integer ncount = 0;
结果依旧一样,难道是Integer 类型的问题?,那我们尝试使用类试试
static public class CA{
}
static public class MyRunable implements Runnable{
private CA lock = new CA();
...
//synchronized (ncount)
synchronized (lock){
...
}
}
发现替换成类之后结果就符合预期。那么synchronized Interger为什么会失败呢?看一下线程调用堆栈
上图中,每一个线程锁住的资源其实都并非是同一个,这就可以解释为什么对Integer类型进行加锁仍然是非线程安全的。其实Synchronized锁的是对象,也就是identityHashCode所指向的内存地址中的对象实例(根据对象内存地址生成散列值)
既然Synchronized是根据identityHashCode 所指向的内存地址中的对象实例来锁的,那么说明Integer的identityHashCode是发生了变化的,而CA是固定的。下面看日志:
最后总结下,synchronized(Integer)时,当值发生改变时,基本上每次锁住的都是不同的对象实例,想要保证线程安全,推荐使用AtomicInteger之类会更靠谱。
参考博客:
https://www.iteye.com/blog/gao-xianglong-2396071