关于如何在Integer上实现synchronized

一、产生了什么问题(What)

设想这样一个场景,我定义了一个全局静态变量count来统计次数,在多线程的情景下,我们的一个解决方案就是对count对象操作时进行同步。然后我们写出了这样一段代码:

public class Main {
   static Integer count=0;



    public static void main(String[] args) {

        Runnable run=()->{
            //在count对象上进行同步
            synchronized (count){
                System.out.println(Thread.currentThread().getName()+"进入");
                count++;
                try {
                    //为了演示效果,阻塞线程
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getName()+"退出");
                }
            }
        };

        Thread t1=new Thread(run,"thread-1");
        Thread t2=new Thread(run,"thread-2");
        t1.start();

        try {
            //主线程休眠100ms,以保证线程t1修改count
            Thread.sleep(1000);
            t2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在理想情况下,允许结果应该为:

//0s后
thread-1进入
//5s后
thread-1退出
//5s后
thread-2进入
//10s后
thread-2退出

然后,实际允许结果为:

//0s后
thread-1进入
//1s后
thread-2进入
//5s后
thread-1退出
//6s后
thread-2退出

可见,在Integer类型的变量count上同步失败。

二、问题怎么产生的(Why)

在count上同步失败,说明t1和t2线程拿到的不是同一个锁,也就是说count对象的地址发生了改变。下面借助joI-core来验证地址是否发生改变:
测试代码:

import org.openjdk.jol.vm.VM;

public class Main {
   static Integer count=0;
    public static void main(String[] args) {
        System.out.println(VM.current().addressOf(count));
        count++;
        System.out.println(VM.current().addressOf(count));
    }
}

运行结果:

3590308504
3590308520

count++操作使对象地址发生了改变,因此导致同步失败。事实上对count对象做运算都会导致对象地址改变。类似的还有String以及基本类型的包装类。

三、如何解决(How)

3.1 通过Integer.valueOf()获取到[-128,127]的对象。

以下是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);
    }

如果i在[-128,127]范围内,是直接返回cache中的对象的,自身的count对象不会地址不会发生改变。
这个方法虽然可行,但最大只能计数到127。

3.1 将++操作封装成一个方法,对方法同步。

将count++操作封装为一个同步的方法:

   static Integer count=0;
    public static synchronized void add(){
        count++;
    }

Runable调用add()方法:

  Runnable run=()->{
          //调用同步方法
          add();
        };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值