Java多线程学习笔记
1. volatile关键字介绍
1.1 初始volatile关键字
下面是一个Demo,其中线程Updater对共享变量进行修改,另一个线程一直读取共享变量。
public class MainClass {
// 常量5
final static int MAX = 5;
// 静态变量,模拟多线程的共享资源
static int init_value = 0;
public static void main(String args[]){
Thread readThread = new Thread(new Runnable() {
public void run() {
int local_value = init_value;
while(local_value<MAX){
if (init_value != local_value){
System.out.printf("the init_value is updated to [%d]\n",init_value);
local_value = init_value;
}
}
}
},"Reader");
Thread updateThread = new Thread(new Runnable() {
public void run() {
int local_value = init_value;
while(local_value<MAX){
System.out.printf("the init_value is changed to [%d]\n",++local_value);
init_value = local_value;
try{
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e ){
e.printStackTrace();
}
}
}
},"Updater");
readThread.start();
updateThread.start();
}
}
打印结果如下:
the init_value is changed to [1]
the init_value is changed to [2]
the init_value is changed to [3]
the init_value is changed to [4]
the init_value is changed to [5]
从结果可知Updater线程修改共享变量后,Reader线程并未打印,即Reader线程中读取的共享变量的值并未随之发生变化。这是为什么呢?
怀着疑问将上述定义的共享变量加上volatile关键字后再次执行demo:
static volatile int init_value = 0;
打印结果如下:
the init_value is changed to [1]
the init_value is updated to [1]
the init_value is changed to [2]
the init_value is updated to [2]
the init_value is changed to [3]
the init_value is updated to [3]
the init_value is changed to [4]
the init_value is updated to [4]
the init_value is changed to [5]
the init_value is updated to [5]
从结果可以看出,当Updater线程修改了共享变量的值后,Reader线程获取到的共享变量的值随之发生变化。
从这个demo可以看出,volatile关键字有同步多线程读写共享变量的效果,后续就此进行讨论。
1.2 机器硬件CPU
在计算机中运算操作由CPU的寄存器来完成,通常CPU从主存RAM中访问数据,在寄存器中处理数据。虽然CPU处理速度随着时代越来越快,但是内存访问速度却相当较慢,由此导致CPU整天吞吐量不高的问题。为此在CPU和主存之间增加了缓存,如下图所示:
image.png
这种设计虽然提高了CPU的吞吐量,但是多CPU同时执行的时候由于读取和写入都是在各自的缓存中并且还没同步到主存中,这就导致了数据不一致的问题。
为解决这个问题,通常有两种办法:一是通过总线加锁的方式,使得一次只有一个CPU可以访问这个变量的内存,但是这种方式效率较低;二是通过缓存一致性协议的方式来解决,这种方式的思路是当其中一个CPU要修改数据的时候,发出信号通知其他CPU将该共享变量在每个CPU对应的缓存置为无效状态,使其他CPU在读取该变量的时候从主从中读取数据。
1.3 Java内存模型
类比于CPU的缓存模型,Java的内存模型如下:
image.png
其中每个线程也有各自的本地内存保存共享变量的副本。同CPU缓存模型,为了解决多线程数据不一致的问题,当其中一个线程修改共享变量后,先将其从本地内存同步到主存中,于此同时其他线程本地内存中的该变量已经失效,其他线程想获取该变量只能从主存中读取最新的值,这样就解决了多线程数据不一致的问题。
2. 并发编程三大特性
2.1 原子性
原子性指的是多次操作要么全部执行要么都不执行,常见的例子就是银行转账的案例。
- 简单的读取和赋值操作是原子性的,如x=10是原子操作,但是y=x,y++等操作都不是原子操作,即多个原子性的操作在一起就不是原子性操作了。
volatile关键字不保证数据的原子性,synchronized关键字保证原子性。另外原子类型变量也可以保证原子性。
2.2 可见性
可见性指的是一个线程对共享变量修改,其他线程可以立即看到修改后的最新值。在上面的例子中可知使用volatile关键字具备此特性。当然synchronized关键字由于控制了同一时刻只有一个线程获得锁也能保证一致性。
有序性
所谓有序性指的是代码在执行过程中的先后顺序,由于在编译以及运行期的优化导致代码的执行顺序未必就是编写时的顺序,即发生指令的重排序。在单线程环境下,无论如何重排序执行结果和代码顺序一样,但是多线程环境下,就会有问题。为此Java提供了三种保证有序性的方式:
- 使用volatile关键字保证有序性
- 使用synchronized关键字来保证有序性
- 使用显示锁Lock来保证有序性
网友评论