解决内建锁锁不住Integer类型全局变量问题的两种方法

本文探讨了在Java中内建锁无法有效保护Integer全局变量自增自减操作的问题,原因涉及JMM内存模型、工作内存与主内存的数据同步。文章指出,当Integer值在[-128, 127]范围内时,由于常量池的存在,自增操作可能看似线程安全。提出两种解决方案:一是锁住类的.class对象,二是锁定任意对象实例,确保锁住同一对象以实现线程安全。同时强调,使用volatile关键字在此场景下无效。" 100739440,9041434,深入理解白盒测试技术,"['测试方法', '白盒测试', '逻辑覆盖', '代码审查', '程序结构']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

解决内建锁锁不住Integer类型全局变量自增自减问题的两种方法

了解这种问题的解决方法之前,我们先来看一下产生此种问题的原因

class MyThread implements Runnable{
    static Integer num=0;

    public static Integer getNum() {
        return num;
    }

    @Override
    public void run() {
        for(int i=0;i<10000;i++){
            synchronized (num){
                num++;
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        Thread threadA=new Thread(myThread,"线程A");
        Thread threadB=new Thread(myThread,"线程B");
        threadA.start();
        threadB.start();
        threadA.join();
        threadB.join();
        System.out.println("num最终结果为:"+MyThread.getNum());
    }
}
//num最终结果为:15692
//num最终结果为:16178
//...每次输出结果均不同,但并不与预期结果20000相同

如上代码中num为静态变量,多个线程对同名变量进行修改操作时虽然使用内建锁对其进行上锁,但通过运行结果我们可以看出上锁失败依旧存在多并发问题。那么出现该问题的原因是什么呢?我们通过学习JMM内存模型可以知道,内存分为主内存与工作内存,工作内存中为主内存变量的副本,主内存与工作内存之间的关系为若某一线程要对变量进行修改操作,那么一定是先将该变量内容拷贝至自身的工作内存中,修改完毕后再将修改完毕的最新值拷贝至主内存中。那么此种执行模式在多线程情况下就势必存在数值的延迟问题,故导致两个线程对同名变量修改后与预期结果不同。

全局变量自增自减(num++,num–)实际上这是存在两步运算的,所以就极有可能线程A在进行完加法后,线程B由于系统调度的原因执行完毕将最新数值拷贝回主内存,之后再次执行线程A,此时线程A不会从主线程中拷贝最新数值而是接着执行赋值运算,执行完毕后再将数据拷贝回主内存,此时变量内容就出现了偏差。

在这里插入图片描述

关于以上问题还存在一个现象不得不注意,当我们将变量自增自减的范围修改一下时,结果又会正确。

class MyThread implements Runnable{
    static Integer num=0;

    public static Integer getNum() {
        return num;
    }

    @Override
    public void run() {
        synchronized (num){
            for(int i=0;i<127;i++){
                num++;
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        Thread threadA=new Thread(myThread,"线程A");
        Thread threadB=new Thread(myThread,"线程B");
        threadA.start();
        threadB.start();
        threadA.join();
        threadB.join();
        System.out.println("num最终结果为:"+MyThread.getNum());
    }
}
//num最终结果为:254

看到这个范围127有没有很熟悉呢?Integer类为包装类,在[-128,127]区间数据存储于常量池中,也就是说当基本类型在此范围时,数值进行运算时进行自动装箱封装至同一对象中,用内建锁锁变量就相当于锁了同一对象,故任意时刻都只有一个线程能获取锁,运算结果正确。超出此范围是自动装箱过程实际上是在不断地创建新对象,也就是说此时锁变量锁的一定不是同一对象,故运算结果出现偏差。

那么解决上述问题的方法是哪些呢?怎样才能使出现此种现状时线程安全。

第一种方法:锁类.class对象,此种方式一定能锁住,原因是任何一个对象在JVM中有且只有一个class对象。

class MyThread implements Runnable{
    static Integer num=0;

    public static Integer getNum() {
        return num;
    }

    @Override
    public void run() {
        synchronized (MyThread.class){//内建锁锁MyThread类的class对象
            for(int i=0;i<20000;i++){
                num++;
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        Thread threadA=new Thread(myThread,"线程A");
        Thread threadB=new Thread(myThread,"线程B");
        threadA.start();
        threadB.start();
        threadA.join();
        threadB.join();
        System.out.println("num最终结果为:"+MyThread.getNum());
    }
}
//num最终结果为:40000

第二种方法:创建任意一个对象,内建锁锁此对象即可(因只有一个该对象)。

class MyThread implements Runnable{
    static Integer num=0;
    private final Object numLock=new Object();//一般创建此种对象时使用final关键字,因不可修改

    public static Integer getNum() {
        return num;
    }

    @Override
    public void run() {
        synchronized (numLock){
            for(int i=0;i<20000;i++){
                num++;
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        Thread threadA=new Thread(myThread,"线程A");
        Thread threadB=new Thread(myThread,"线程B");
        threadA.start();
        threadB.start();
        threadA.join();
        threadB.join();
        System.out.println("num最终结果为:"+MyThread.getNum());
    }
}
//num最终结果为:40000

但切记,此种多线程并发情况下变量加volatile关键字修饰是没有用的,虽说保证了可见性,但锁没有锁住,结果还是有误差。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值