1.volatile
volatile是java虚拟机提供的轻量级同步机制
2.特性
保证可见性,不保证原子性,禁止指令重排(有序性)
2.1 可见性
首先要知道JMM,就是java内存模型(可见性、原子性、有序性)
这是一个抽象概念;内存分为主内存和工作内存。主内存主要存放共享变量等等,用于数据共享的
而工作内存是线程操作资源的一个区域,每个线程都有自己的工作内存。
资源的操作流程主要分为以下三步:
1.线程从主内存中copy取出需要操作的资源
2.线程操作资源
3.写回主内存(在CAS中,需要比较此时主内存的内容和之前拿到的内容是否相同)
而在线程写回主内存后,需要通知其他线程这个共享变量已经被修改,如果修改了,其他线程就重
新从主内存中获取,这就叫做可见性
下面是一段代码演示:
/**
* volatile的可见性
*/
public class TestVolatile {
public static void main(String[] args) {
SourceTest sourceTest = new SourceTest();
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {e.printStackTrace();}
sourceTest.addNum();
System.out.println(Thread.currentThread().getName() + "修改a后: " + sourceTest.a);
},"sub thread").start();
while (sourceTest.a == 0){
//进入循环
System.out.println("主线程进入循环取得a的值:" + sourceTest.a);
}
System.out.println(Thread.currentThread().getName() + "得到a的值: " + sourceTest.a);
}
}
class SourceTest{
//int a = 0;//此时不加volatile主线程进入死循环,进程无法结束
volatile int a = 0;
void addNum(){
a = 60;
}
}
输出:
...
主线程进入循环取得a的值:0
主线程进入循环取得a的值:0
sub thread修改a后: 60
main得到a的值: 60
2.2 不保证原子性
原子性:不可分割,完整性,即线程在处理某个业务时,中间不允许被加塞
public class TestAtomic {
public static void main(String[] args) throws InterruptedException {
DataSource dataSource = new DataSource();
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000 ; j++) {
dataSource.getAndIncre();
}
},String.valueOf(i)).start();
}
//等待以上线程执行完成,main线程取值
//TimeUnit.SECONDS.sleep(5);
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t 最终number: " + dataSource.number);
}
}
class DataSource{
volatile int number = 0;
void getAndIncre(){
number++;
}
}
输出结果:
main 最终number: 19081
以此可看出volatile不保证原子性(i++线程不安全=>主要是i++先自增,再返回自增之前的值,导致某时通知判断错误)
解决无法保证原子性
1.加sync
2.AtomicInteger
AtomicInteger atomicInteger = new AtomicInteger();
void getAndIncre(){
//number++;
atomicInteger.getAndIncrement();
}
2.3 禁止指令重排(有序性)
计算机在执行程序时,为了提高性能,编译器和处理器会对指令进行重排:
源代码>(编译器优化的重排>指令并行的重排>内存系统的重排)>最终执行的指令
在单线程环境中,可以确保最终执行的结果与代码顺序一致
处理器在重排时必须考虑指令之间的数据依赖性
而多线程环境中线程交替执行,两个线程使用的变量就无法确定能保持一致性