最近在学习 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;
}
对静态方法加锁会锁住整个类,同样可以达到目的。