并发和并行的概念:
并发:指在某个时间段内,多任务快速交替处理的能力
并行:指在同一时刻,有多任务在多个处理器上同时执行
并发编程的三大特性
并发场景中多个线程同时工作,存在同步、互斥、分工的问题,所以并发编程的三大特性:
1:可见性
当一个线程修改了共享变量的值,其他线程能够看到修改的值
Java中可见性如何保证?
- jvm层面 storeLoad内存屏障
以下4种方式去从java代码角度:
- 通过 volatile 关键字保证可见性。
实现原理:
工作内存修改共享变量后立即同步回主内存;
回写到主内存会导致其他工作内存的缓存无效;
其他工作内存使用时必须从主内存去重新获取
- 显示调用内存屏障方法 UnsafeFactory.getUnsafe().storeFence()
- 通过 synchronized关键字。
- 通过 Lock保证有序性
有时候是Lock锁和volatile联合使用,因为锁的临界区代码可能会发生重排序的情况,多线程的情况下还需要volatile加一层内存屏障,防止重排序,demo是单例模式double-check locking
上下文切换
Thread.yield();<br />2:原子性<br />一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行<br />3:有序性<br />程序执行的顺序按照编码代码的先后顺序执行。 指令重排导致
JMM
JMM只是一种抽象的概念,定义了共享内存系统中多线程程序读写操作行为的规范,解决由于多线程通过共享内存进行通信时,由于CPU多级缓存、处理器优化、指令重排等导致的内存访问问题。保证了并发场景下的可见性、原子性和有序性.
用JMM屏蔽掉各种硬件和操作系统的内存访问差异,实现Java程序在各种平台下都能达到一致的并发效果。
JMM与硬件内存架构的关系
Java内存模型与硬件内存架构之间存在差异。硬件内存架构没有区分线程栈和堆。对于硬 件,所有的线程栈和堆都分布在主内存中。部分线程栈和堆可能有时候会出现在CPU缓存中和 CPU内部的寄存器中。如下图所示,Java内存模型和计算机硬件内存架构是一个交叉关系
经典的两个线程修改主存变量flag的例子
线程A while(flag){ } 当flag为true一直占用时间片且循环内部方法的执行时间很短,flag本地内存就不会被淘汰
public class VisibilityTest {
// storeLoad JVM内存屏障 ----> (汇编层面指令) lock; addl $0,0(%%rsp)
// lock前缀指令不是内存屏障的指令,但是有内存屏障的效果 缓存失效
private volatile boolean flag = true;
private Integer count = 0;
public void refresh() {
flag = false;
System.out.println(Thread.currentThread().getName() + "修改flag:"+flag);
}
public void load() {
System.out.println(Thread.currentThread().getName() + "开始执行.....");
while (flag) {
//TODO 业务逻辑
count++;
//JMM模型 内存模型: 线程间通信有关 共享内存模型
//没有跳出循环 可见性的问题
//能够跳出循环 内存屏障
//UnsafeFactory.getUnsafe().storeFence();
//能够跳出循环 ? 释放时间片,上下文切换 加载上下文:flag=true
//Thread.yield();
//能够跳出循环 内存屏障
//System.out.println(count);
//LockSupport.unpark(Thread.currentThread());
//shortWait(1000000); //1ms
//shortWait(1000);
// try {
// Thread.sleep(1); //内存屏障
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
System.out.println(Thread.currentThread().getName() + "跳出循环: count=" + count);
}
public static void main(String[] args) throws InterruptedException {
VisibilityTest test = new VisibilityTest();
// 线程threadA模拟数据加载场景
Thread threadA = new Thread(() -> test.load(), "threadA");
threadA.start();
// 让threadA执行一会儿
Thread.sleep(1000);
// 线程threadB通过flag控制threadA的执行时间
Thread threadB = new Thread(() -> test.refresh(), "threadB");
threadB.start();
}
public static void shortWait(long interval) {
long start = System.nanoTime();
long end;
do {
end = System.nanoTime();
} while (start + interval >= end);
}
}