如果多个线程在做同一件事情的时候。
- 原子性 Synchronized, AtomicXXX、Lock、
- 可见性 Synchronized, volatile
- 有序性 Synchronized,volatile
原子性问题
在下面的案例中,演示了两个线程分别去去调用 demo.incr 方法来对 i 这个变量进行叠加,预期结果应该是20000,但是实际结果却是小于等于20000的值。
public class Demo {
int i = 0;
public void incr(){
i++;
}
public static void main(String[] args) {
Demo demo = new Demo();
Thread[] threads=new Thread[2];
for (int j = 0;j<2;j++) {
threads[j]=new Thread(() -> { // 创建两个线程
for (int k=0;k<10000;k++) { // 每个线程跑10000次
demo.incr();
}
});
threads[j].start();
}
try {
threads[0].join();
threads[1].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo.i);
}
}
问题的原因
在上面这段代码中,count++是属于Java高级语言中的编程指令,而这些指令最终可能会有多条CPU指令来组成,而count++最终会生成3条指令, 通过javap -v xxx.class 查看字节码指令如下。
public incr()V
L0
LINENUMBER 13 L0
ALOAD 0
DUP
GETFIELD com/gupaoedu/pb/Demo.i : I // 访问变量i
ICONST_1 // 将整形常量1放入操作数栈
IADD // 把操作数栈中的常量1出栈并相加,将相加的结
果放入操作数栈
PUTFIELD com/gupaoedu/pb/Demo.i : I // 访问类字段(类变量),复制给Demo.i这个变
量
这三个操作,如果要满足原子性,那么就需要保证某个线程在执行这个指令时,不允许其他线程干扰,然后实际上,确实会存在这个问题。
图解问题本质
前面我们说过,一个CPU核心在同一时刻只能执行一个线程,如果线程数量远远大于CPU核心数,就会发生线程的切换,这个切换动作可以发生在任何一条CPU指令执行完之前。对于 i++ 这三个cpu指令来说,如果线程A在执行指令1之后,做了线程切换,假设切换到线程B,线程B同样执行CPU指令,执行的顺序如下图所示。就会导致最终的结果是1,而不是2
这就是在多线程环境下,存在的原子性问题,那么,怎么解决这个问题呢?大家认真观察上面这个图,表面上是多个线程对于同一个变量的操作,实际上是count++这行代码,它不是原子的。所以才导致在多线程环境下出现这样一个问题。也就是说,我们只需要保证,count++这个指令在运行期间,在同一时刻只能由一个线程来访问,就可以解决问题。这就需要引出到今天的课程内容, 同步锁Synchronized