Java 并发笔记(一):synchronized 对 Integer 加锁的注意事项

最近在学习 Java 并发的相关知识,发现 synchronized 锁对 Integer 等基本类型的包装类没有效果,示例如下:

public class AccountingSyncBad implements Runnable {
    static volatile Integer i = 0;

    public static void increase() {
        synchronized(i){
            ++i;
        }
    }
    @Override
    public void run() {
        for(int j = 0; j < 1000000; ++j) {
            increase();
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new AccountingSyncBad());
        Thread t2 = new Thread(new AccountingSyncBad());
        t1.start();
        t2.start();
        try{
            t1.join();
            t2.join();
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(i);
    }
}

运行前以为结果会是 2000000,因为代码已经对 i 进行了加锁操作。但运行多次后结果均小于 2000000。一开始认为是 synchronized 的问题,后来才发现问题出在 Integer 类。

Integer 类内部属性的定义是 private final int value;,当 value 变化时就会产生一个新的 Integer:

public class IntegerTest{

    public static void main(String[] args) {
        Integer i = 3;
        System.out.println(System.identityHashCode(i));
        ++i;
        System.out.println(System.identityHashCode(i));
    }
}

输出结果如下:

2018699554
1311053135

可以看出 i 前后所指向的对象地址不同,即加法操作前后引用的不是同一个对象。因此对 Integer 加锁是没有意义的,每一次加锁锁住的都是不同的对象。

解决这个问题有以下几个方法:

对 Integer 进行包装

public class AccountingSyncBad implements Runnable {
    static Wrapper i = new Wrapper();
    public static void increase() {
        synchronized(i){
            ++i.sum;
        }
    }
    @Override
    public void run() {
        for(int j = 0; j < 1000000; ++j) {
            increase();
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new AccountingSyncBad());
        Thread t2 = new Thread(new AccountingSyncBad());
        t1.start();
        t2.start();
        try{
            t1.join();
            t2.join();
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(i.sum);
    }
}

class Wrapper{
    public Integer sum = 0;
}

使用一个 Wrapper 类对 Integer 进行包装,此时加锁会锁住同一个对象。

对类的实例进行加锁

public class AccountingSyncBad implements Runnable {
    // 定义一个类的实例
    static AccountingSyncBad instance = new AccountingSyncBad();
    static volatile Integer i = 0;

    public static void increase() {
        // 对类的实例加锁
        synchronized(instance){
            ++i;
        }
    }
    @Override
    public void run() {
        for(int j = 0; j < 1000000; ++j) {
            increase();
        }
    }

    public static void main(String[] args) {
        // 用同一个实例创建线程
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        try{
            t1.join();
            t2.join();
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(i);
    }
}

class Wrapper{
    public Integer sum = 0;
}

在创建线程之前先定义一个类的实例,用这个实例创建两个线程,执行加法时对实例加锁,同样可以达到目的。

对静态方法进行加锁

public class AccountingSyncBad implements Runnable {
    static AccountingSyncBad instance = new AccountingSyncBad();
    static volatile Integer i = 0;

    public synchronized static void increase() {
        ++i;
    }
    @Override
    public void run() {
        for(int j = 0; j < 1000000; ++j) {
            increase();
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new AccountingSyncBad());
        Thread t2 = new Thread(new AccountingSyncBad());
        t1.start();
        t2.start();
        try{
            t1.join();
            t2.join();
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(i);
    }
}

class Wrapper{
    public Integer sum = 0;
}

对静态方法加锁会锁住整个类,同样可以达到目的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值