首先举一个例子:
定义一个 CountThread 类,如下,在 run 函数中进行全局变量的自增操作。
public class CountThread extends Thread{
private int count = 0;
@Override
public void run() {
count++;
System.out.println(Thread.currentThread().getName() + " - " + count);
}
}
调用的测试类如下:
public class Run {
public static void main(String[] args) throws InterruptedException{
CountThread countThread = new CountThread();
for(int i=0; i<1000; i++){
Thread thread = new Thread(countThread);
thread.start();
}
}
}
运行结果如下(截取部分):
Thread-1 - 1
Thread-2 - 3
Thread-3 - 2
Thread-4 - 4
Thread-5 - 5
Thread-8 - 6
Thread-7 - 7
Thread-11 - 8
Thread-17 - 12
Thread-21 - 13
Thread-15 - 14
Thread-23 - 16
Thread-10 - 17
Thread-14 - 18
Thread-18 - 20
Thread-19 - 20
...
Thread-980 - 978
Thread-981 - 979
Thread-983 - 981
Thread-982 - 981
Thread-984 - 982
Thread-986 - 984
Thread-985 - 984
Thread-989 - 985
Thread-987 - 986
Thread-990 - 987
Thread-991 - 988
Thread-993 - 989
Thread-994 - 990
Thread-995 - 992
Thread-992 - 993
Thread-988 - 991
Thread-996 - 994
Thread-997 - 995
Thread-999 - 997
Thread-998 - 996
Thread-1000 - 998
根据截取的部分可以看到某些线程,执行 count++ 后输出了同样的结果,比如 Thread-18 和 Thread-19,二者均输出 20。同样还有 Thread-985 和 Thread-986,二者均输出 984。造成这个问题的原因就是自增操作并不是原子性的,自增的方法要分为如下的三步来执行:
一.将 count 的值读入寄存器中;
二.将 count 加 1;
三.将 count 保存回内存中。
如果A线程在执行完第一步后,CPU交给B线程来执行,B线程也将 count 的值读入寄存器,此时,AB两个线程中 count 的值是一样的,A,B两个线程执行完后,返回的结果是同一个结果。
将程序进行修改,加入 synchronized 关键字:
public class CountThread extends Thread{
private int count = 0;
@Override
public synchronized void run() {
count++;
System.out.println(Thread.currentThread().getName() + " - " + count);
}
}
测试程序不变,运行后,结果如下:
Thread-1 - 1
Thread-6 - 2
Thread-5 - 3
Thread-8 - 4
Thread-4 - 5
Thread-10 - 6
Thread-3 - 7
Thread-2 - 8
Thread-12 - 9
Thread-11 - 10
...
Thread-980 - 980
Thread-981 - 981
Thread-982 - 982
Thread-983 - 983
Thread-984 - 984
Thread-985 - 985
Thread-986 - 986
Thread-987 - 987
Thread-988 - 988
Thread-989 - 989
Thread-990 - 990
Thread-991 - 991
Thread-992 - 992
Thread-993 - 993
Thread-994 - 994
Thread-995 - 995
Thread-996 - 996
Thread-997 - 997
Thread-998 - 998
Thread-999 - 999
Thread-1000 - 1000
我们可以看到,Thread 的编号并不是按照先后顺序来执行的,这是因为先 start() 方法的线程并不一定先执行,而是由于CPU执行某个线程是具有不确定性的。
这里的结果,说明加入了 synchronized 关键字后,所有线程在同步方法中,是每次只有一个在执行的。最终结果也是正确的。