这是看一篇博客时注意到的问题。
/**
* 版权所有:
* 创建日期:2018年1月9日 下午7:21:23
* 创建作者:
* 文件名称:MyThread.java
* 版本: v1.0
* 功能:
* 修改记录:
*/
import java.util.HashSet;
import java.util.Set;
public class MyThread extends Thread {
static Integer i = 0;
@Override
public void run() {
while (true) {
synchronized (i){
if ( i < 100) {
i++;
System.out.println( Thread.currentThread().getName() +" = "+i);
} else {
break;
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread());
Thread t2 = new Thread(new MyThread());
t1.setName("AAAA");
t2.setName("BBBB");
t1.start();
t2.start();
try{
t1.join();
t2.join();
}catch(Exception e) {
e.printStackTrace();
}
}
}
开始时我和博主李学凯想的一样,static修饰的变量 i 是属于MyThread类的,synchronized代码块同一时间最多只能有一个线程执行,那么两个线程应打印数字1至100,且各自线程打印的值是不重复的。但他文中提到他的运行结果并非如此,且我也复现出来一样的结果:
这实际上和 ++i 是不是原子操作无关,而是java装箱的问题。看下面的例子:
Student.java:
public class Student {
private int age ;
public Student(int age) {
this.age = age;
}
public void addAge(){
++age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
MyThread.java:
public class MyThread extends Thread {
static Student student = new Student(0);
@Override
public void run() {
while (true) {
synchronized (i){
if ( student.getAge() < 100) {
student.addAge();
System.out.println( Thread.currentThread().getName() +" = "+student.getAge());
} else {
break;
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread());
Thread t2 = new Thread(new MyThread());
t1.setName("AAAA");
t2.setName("BBBB");
t1.start();
t2.start();
try{
t1.join();
t2.join();
}catch(Exception e) {
e.printStackTrace();
}
}
}
synchronized (i){
if ( student.getAge() < 100) {
student.addAge();
System.out.println( Thread.currentThread().getName() +" = "+student.getAge());
} else {
break;
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread());
Thread t2 = new Thread(new MyThread());
t1.setName("AAAA");
t2.setName("BBBB");
t1.start();
t2.start();
try{
t1.join();
t2.join();
}catch(Exception e) {
e.printStackTrace();
}
}
}
运行多次,发现结果和我们预期的一样:两个线程分别打印了1-100的数字,且不重复。问题出在哪儿呢?
很多中文博客讲synchronized关键字时,都在讲用在方法和代码块,锁类资源和对象的区别,没有讲清楚synchronized
(expression)的用法。
The syntax for a synchronized statement is as follows:
synchronized(expression) {
// synchronized code block
}
Rules
- The expression must be evaluated to a reference type, i.e an object reference.
也就是说,我们代码中 synchronized (student) 两个线程用的锁始终都是同一块内存,起到了线程安全的作用。
而执行synchronized (i)时,i的初始化语句为
static Integer i = 0;
进行了自动装箱,执行Integer.valueOf(int)函数。i指向的内存实际上是缓存数组IntegerCache.cache[]中的元素,
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];//IntegerCache.cache[128] = 0
return new Integer(i);
}
IntegerCache.cache[128] = 0
return new Integer(i);
}
线程执行完if(i<100)语句中i++后,i指向的内存已发生变化,两个线程中上锁的资源已经不一样,也就不存在synchronized (i)代码块只有在一个线程执行结束后才能被另一个线程执行的说法。
解释可能不是很清楚,加一句打印语句再看结果可能好理解些:
public class MyThread extends Thread {
static Integer i = 0;
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName()+"will lock:" +i);
synchronized (i) {
if ( i < 100) {
i++;
System.out.println( Thread.currentThread().getName() +" = "+i);
} else {
break;
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread());
Thread t2 = new Thread(new MyThread());
t1.setName("AAAA");
t2.setName("BBBB");
t1.start();
t2.start();
try{
t1.join();
t2.join();
}catch(Exception e) {
e.printStackTrace();
}
}
}
System.out.println(Thread.currentThread().getName()+"will lock:" +i);
synchronized (i) {
if ( i < 100) {
i++;
System.out.println( Thread.currentThread().getName() +" = "+i);
} else {
break;
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread());
Thread t2 = new Thread(new MyThread());
t1.setName("AAAA");
t2.setName("BBBB");
t1.start();
t2.start();
try{
t1.join();
t2.join();
}catch(Exception e) {
e.printStackTrace();
}
}
}