诡异的线程加锁问题

原创 2016年09月19日 22:12:54

引言:在Java中,对于互斥的代码块,我们需要使用synchronized来进行线程安全的保证,本文将针对某个synchronized的锁实例中发生的问题来分析。

1. 测试代码

直接上代码,简单明了:

public class BadLockOnInteger implements Runnable {
    public static Integer i = 0;
    static BadLockOnInteger instance = new BadLockOnInteger();
    
	@Override
	public void run() {
		for (int j=0; j<1000000; j++) {
			synchronized(i){
				i++;
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(instance);
		Thread t2 = new Thread(instance);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		
		System.out.println(i);
	}
}
预期行为结果: 2000000

实际的行为结果:1584637,1160643,.....

貌似结果是随机的,且每次的结果都有不同,好像哪里出了问题.......

2.  代码分析

 这段代码其实很简单,就是针对i进行累加,每个线程增加1000000次,两个线程应该是2000000,针对两个线程使用了synchronized来保证互斥区域的线程安全。但是结果确实远远小于2百万的预期结果,只能证明这个synchronized的锁没有起到应有的作用,synchronized(i)好像没有问题呀,那问题处在哪里呢?

3. 原因分析

 在实际的代码执行过程中,i++在真实执行时变成了下面这个样子?

i = Integer.valueOf(i.intValue() + 1)
  在我们进一步查看起Integer.valueOf()的源代码,我们可以看到: 

 public static Integer valueOf(int i) {

       assert IntegerCache.high >= 127;

       if (i >= IntegerCache.low && i <= IntegerCache.high)

            return IntegerCache.cache[i + (-IntegerCache.low)];

       return new Integer(i);
   } 

  Integer.valueOf() 实际上一个工厂方法,它会倾向于返回一个代表制定数值Integer实例,因此,i++的本质是,创建了一个新的Integer对象,并将它的引用赋给i.

  因此我们就知道两个线程每次加锁都加在了不同的对象实例上,从而导致对临界区代码控制出现问题。

  以下是摘在Integer.class的源代码:

    /**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value;
在其内部实现上,value是一个final类型的整形变量,其在创建之后,就无法被改变了,故在我们尝试修改其值的过程中,只能创建新的Integer对象。

4. 问题修复

修正这个问题只需要将

synchronized(i)
替换为:

synchronized(instance)
5. 总结

 synchronized锁定的对象Integer在实际的代码中,是使用final来指定的。所以每次值的改变都会创建新的Integer对象;同样的道理类似于Long, 之类的都是这样的情况,所以最好不要作为锁对象,因为其有可能在值变更的时候创建出新对象,故锁对象发生了变更,锁机制失效。


 

相关文章推荐

线程加锁两次-死锁问题实验

    今天遇到了这个问题,晚上回来写个例子试试,试试证明还是会死锁的。一个不同线程分别加锁的例子,这种情况是不会死锁的。#include #include pthread_mutex_t m...

c++线程加锁

  • 2013年01月13日 12:29
  • 43KB
  • 下载

Java线程加锁方式

synchronized:同步锁关键字 Java中的锁:对象锁和类锁 对象锁:顾名思义给对象上锁 当A线程访问一个object的时候,首先会获取该对象的对象锁,然后访问锁定的代码,而B线程访问...

IOS中实现线程加锁的几个方法

转载自: http://www.tanhao.me/tag/nslock Objective-C中不同方式实现锁(一) 为什么需要使用锁,当然熟悉多线程的你,自然不会对它觉得陌...

dispatch_semaphore 的用法(线程加锁)

转自:综合 dispatch_semaphore 信号量基于计数器的一种多线程同步机制。在多个线程访问共有资源时候,会因为多线程的特性而引发数据出错的问题。     dispatch_queu...

java多线程加锁

package com.zhlk.thread; public class TraditionalThreadSync { /** * 创建日期:2017-3-4下午10:53:49 作者:...

C/C++ Windows API——多线程加锁与临界区域

// MutexDemo.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include //createMutex #include //预先声明类 cla...

iOS多线程加锁

在iOS中有几种方法来解决多线程访问同一个内存地址的互斥同步问题: 方法一,@synchronized(id anObject),(最简单的方法) 会自动对参数对象加锁,保证临界区内的代码线程安全...
  • Kaiccy
  • Kaiccy
  • 2016年07月17日 18:59
  • 143

多线程加锁

Linux pthread_mutex演示程序 C++语言: Linux pthread_mutex演示程序 Linux 下pthread 中使用mutex 进行互斥的程序和结果 #...

关于java对资源加锁无效的问题

看编程思想一书,也写了段代码,发现对static的资源加锁无效,左思右想终于找出了原因,现贴出与大家分享。 import java.util.concurrent.ExecutorService; ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:诡异的线程加锁问题
举报原因:
原因补充:

(最多只允许输入30个字)