• synchronized 加锁Integer对象(数据重复)详解


    场景描述:多线程输出1到100,对静态Integer对象加锁,synchronized代码块中操作Integer对象,发生线程安全问题(数据重复)

    代码:

    public class MyRunnableTest implements Runnable {
        public static Integer i = new Integer(0);
    
        @Override
        public void run() {
            while(true){
                synchronized (i) {
                    if(i<100){
                        i++;
                        System.out.println(Thread.currentThread()+"i = " + i);
                    }else {
                        break;
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new MyRunnableTest());
            Thread t2 = new Thread(new MyRunnableTest());
            t1.start();
            t2.start();
        }
    
    }

    运行结果:

    Thread[Thread-0,5,main]i = 1
    Thread[Thread-1,5,main]i = 3
    Thread[Thread-1,5,main]i = 4
    Thread[Thread-1,5,main]i = 5
    Thread[Thread-1,5,main]i = 6
    Thread[Thread-0,5,main]i = 5
    Thread[Thread-1,5,main]i = 7
    Thread[Thread-0,5,main]i = 8
    Thread[Thread-1,5,main]i = 9
    Thread[Thread-0,5,main]i = 10
    Thread[Thread-1,5,main]i = 11
    Thread[Thread-0,5,main]i = 12
    Thread[Thread-1,5,main]i = 13
    

     

    从运行结果中可以发现发生了线程安全问题,为什么呢?为什么synchronized无效了。

    我的排查思路:

    1、因为没有进行任何的额外操作,所以首先定位问题在i++处

    通过javap分析字节码命令,总结:

    可以看出:i++的实际操作为:Integer.valueOf(Integer.intValue(i)+1)

    我们在看看Integer中vauleOf的源码:

    public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
    
    cache[]数组初始化: 
      cache = new Integer[(high - low) + 1];
      int j = low;
      for(int k = 0; k < cache.length; k++)
      cache[k] = new Integer(j++);

    其中low为-128,hight为127。可以了解到,当i的值为-128~127时,是从IntegerCache中取的(可以理解为从缓存中取的),超过部分是new出来的对象。

    2、目前我们慢慢开始解开了问题的面纱,每当i发生自增后,所对象改变了,这里我们还需要清楚一下,synchronized在锁对象发生改变时(测试发现,引用类型对象的内在属性变化不会释放锁)会立即释放锁。所以这里就会出现线程安全问题,而且在单核的运行环境下,所有的线程是并发执行而不是并行执行,当我们运行到system.out.println时,锁已经释放了,假如这边t1线程(当前运行的线程,i=1的情况)释放CPU资源,t2执行,这时i等于2(i++之前),当执行到system.out.println时释放cpu资源(此时i=3),t1执行,i此时已经为3了,所以输出3,在释放cpu资源,t2执行,输出3,这时出现了输出重复值的情况。

  • 相关阅读:
    eval()一个有意思的PHP函数
    PHP error_reporting() 函数
    网络编程基础--协程--greenlet切换---gevent自动识别 IO ---
    网络编程基础--多线程---concurrent.futures 模块---事件Event---信号量Semaphore---定时器Timer---死锁现象 递归锁----线程队列queue
    rpm -qa 查找文件
    Linux Gvim shell 创建第一个shell脚本
    centos7修改网卡名称为eth0-技术流ken
    pxe+kickstart自动化批量安装系统详解-技术流ken
    cobbler单台服务器实现批量自动化安装不同版本系统-技术流ken
    cobbler批量安装系统使用详解-技术流ken
  • 原文地址:https://www.cnblogs.com/jswang/p/9099926.html
Copyright © 2020-2023  润新知