在Java中,每一个线程有一块工作内存区,其中存放着被所有线程共享的主内存中的变量的值的拷贝。当线程执行时,它在自己的工作内存中操作这些变量。
为了存取一个共享的变量,一个线程通常先获取锁定并且清除它的工作内存区,保证该共享变量从所有线程的共享内存区正确地装入线程的工作内存区,当线程解锁时保证该工作内存区中的变量的值写回到共享内存中。
一个线程可以执行的操作有使用、赋值、装载、存储、锁定和解锁。而主内存可以执行的操作有读、写、锁定和解锁,每一个操作都是原子操作。
[caption id=“attachment_1847” align=“aligncenter” width=“242”] 线程工作内存与主内存交互[/caption]
因为每一个线程都有自己的工作内存区,因此当一个线程改变自己的工作内存区中的数据时,对其他线程来说是不可见的。为此,可以使用volatile关键字迫使所有线程均读写主内存中的对应变量,从而使得volatile变量在多线程之间可见。
声明volatile的变量可以做到以下保证:
- 其他线程对变量的修改,可以及时反映在当前线程中。
- 确保当前线程对volatile变量的修改,能够及时写回到共享主内存中,并被其他线程所见
- 使用volatile声明的变量,编译器会保证其有序性
- 确保内存可见性
- 禁止指令重新排序
当两个线程都对共享内存中的变量i执行自增操作时,可能最终执行的结果只自增了1。因为自增操作本身并不是一个原子操作,它需要完成三个步骤:从共享内存中读取变量、变量加一,将变量写会共享内存。当线程1读取变量i之后时间片用完,线程2读取变量i完成加一再写回共享内存后,线程1继续工作,但是最终写回的只是i的旧值加一的值。所以最终完成的仅仅是一次自增。
而对于禁止指令重新排序而言:编译器在将程序编译成字节码时,为了确保代码运行的速度,会对程序进行优化。
i=1;
i=2;
i=4;
诸如这样的操作,最终会被重新排序为
i=4;
使用了volatile关键字之后,编译器会确保被修饰的变量在执行前前面的所有代码都已经被执行完毕,并确保对volatile变量的操作会按照代码中的顺序执行。
使用++操作进行自增不是原子操作,而使用AtomicInteger进行自增操作是原子操作。
如果喜欢我的文章,可以扫码领红包支持一下