天天看点

Synchronized中使用Integer带来的问题解决方案

作者:千锋教育

今天,给学生讲了多线程中线程安全的问题。

刚下课,就有学生过来问:"亮哥,你不是说在加锁时,使用同一个对象作为锁就能解决线程安全问题么?但是我的代码怎么不行呢?"

我看了下他的代码,如下:

public class TestMain {
public static void main(String[] args) {
Seller s1 = new Seller("张三");
Seller s2 = new Seller("李四");
Seller s3 = new Seller("王五");
s1.start();
s2.start();
s3.start();
}
}
class Seller extends Thread{
private String name;
public static Integer ticket = 10;
public Seller(String name) {
this.name = name;
}           
@Override
    public void run() {
            while(ticket > 0) {
                    System.out.println(name + "开始卖票");
                    synchronized (ticket) {
                            if(ticket > 0) {
                                    try {
                                            Thread.sleep(10);
                                    } catch (InterruptedException e) {
                                            e.printStackTrace();
                                    }
                                    ticket--;
                                    System.out.println(name + "卖出了一张票,还剩"+ticket+"张票");
                            }
                    }
            }
    }
}           

我发现其synchronized方法中的对象是ticket,这是一个有意思的问题。

因为ticket是Integer类型的,在加锁的时候确实是类属性,在多个对象中共享一个,所以说是锁的一个对象也没有问题,但是在下面的代码中又对ticket进行了改变,那么就不是同一个对象了。

打开Integer的源代码可以看到如下代码:

public static Integer valueOf(int i) {
// 当传入的值范围是-128~127之间时,直接返回
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)]; // 相应的下标
return new Integer(i); // 不在此范围则创建一个新对象
}
private static class IntegerCache {
static final int low = -128; // 最小值
static final int high; // 最大值
static final Integer cache[]; // 整型数组           
// 静态代码块,在类加载时执行
static {
    // high value may be configured by property
    int h = 127; // 最大值
    String integerCacheHighPropValue =
        sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    if (integerCacheHighPropValue != null) {
        try {
            int i = parseInt(integerCacheHighPropValue);
            i = Math.max(i, 127);
            // Maximum array size is Integer.MAX_VALUE
            h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
        } catch( NumberFormatException nfe) {
            // If the property cannot be parsed into an int, ignore it.
        }
    }
    high = h; // 如果没有配置,最大值127

    cache = new Integer[(high - low) + 1]; // 数组大小256
    int j = low;
    for(int k = 0; k < cache.length; k++) // 循环范围0~255
        cache[k] = new Integer(j++);

    // range [-128, 127] must be interned (JLS7 5.1.7)
    assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}           

上面的代码意味着Integer在自动装箱和拆箱时,并非是直接对值进行修改,而是返回常量池数组中另一个对象或者创建一个新对象。导致上面的synchronized中的加锁对象,在加锁后进行修改后,变成了另一个对象,那么锁失效是很自然的问题。

那么可以修改如下,可以解决该问题:

public class TestMain {
public static void main(String[] args) {
Seller s1 = new Seller("张三");
Seller s2 = new Seller("李四");
Seller s3 = new Seller("王五");
s1.start();
s2.start();
s3.start();
}
}
class Seller extends Thread{
private String name;
public static Integer ticket = 10;
public Seller(String name) {
this.name = name;
}           
@Override
public void run() {
        while(ticket > 0) {
                System.out.println(name + "开始卖票");
                synchronized (A.obj) {
                        if(ticket > 0) {
                                try {
                                        Thread.sleep(10);
                                } catch (InterruptedException e) {
                                        e.printStackTrace();
                                }
                                ticket--;
                                System.out.println(name + "卖出了一张票,还剩"+ticket+"张票");
                        }
                }
        }
}
}
class A{
  public static final Object obj = new Object();
}           

消息队列RabbitMQ安装配置详解

前端路由+原生JS实现SPA

Linux安装详细步骤(Mac版本)

继续阅读