一、JMM内存模型
1.1 概念
JMM即Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存、CPU指令优化等。
1.2 特性
- 原子性-保证指令不会受到线程上下文切换的影响工
- 可见性-保证指令不会受cpu缓存的影响
- 有序性-保证指令不会受cpu指令并行优化的影响
1.3 可见性
Slf4j(topic = "c.Test04")
public class Test04 {
static Boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while ((flag)){
//...
}
}).start();
Thread.sleep(1000);
flag = false;
}
}
主线程改变了其他线程的结束条件,其他线程无法结束。
原因:JIT即时编译器发现这个flag经常读取,就把它放在工作内存中的高速缓存区,导致每次读取的都是true,外部的共享的主存变量不可见。
解决:共享变量添加volatile修饰(每次读取都是在主存读取,保证可见性)
synchronize也能解决可见性问题。
1.4 synchronize和volatile
- volatile可以保证可见性,可以在本线程内放置指令重排,但不能解决指令交错,即原子性。适用于一个线程写,多个线程读。
- synchronize能解决可见性和原子性。性能较低。
二、设计模式
1.1 两阶段终止模式优化(采用volatile):
@Slf4j(topic = "c.Test05")
public class Test05 {
static volatile Boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
if (!flag)
{
log.debug("退出");
break;
}
try {
log.debug("执行中...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
Thread.sleep(3000);
stop(thread);
}
private static void stop(Thread thread){
flag = false;
thread.interrupt();
}
}
interrupt可以在线程休眠的时候打断进入catch异常后重新循环。
结果:
1.2 犹豫模式(Balking)
@Slf4j(topic = "c.Test06")
public class Test06 {
private static Boolean flag = false;
public static void main(String[] args) {
Test06 test06 = new Test06();
test06.start();
test06.start();
}
private void start(){
synchronized (this){
if(flag)
return;
new Monitor().start();
flag = true;
}
}
}
@Slf4j(topic = "c.Monitor")
class Monitor extends Thread{
@Override
public void run() {
while(true){
log.debug("监控中");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
犹豫模式能够保证只有一个线程启动,因为要读取和操作flag的值,在多线程的情况下必须保证原子性,所以使用synchronize
优化:
@Slf4j(topic = "c.Test06")
public class Test06 {
public static void main(String[] args) throws InterruptedException {
Monitor monitor = new Monitor();
monitor.start1();
Thread.sleep(2000);
monitor.start1();
Thread.sleep(2000);
monitor.stop1();
Thread.sleep(2000);
monitor.stop1();
Thread.sleep(2000);
monitor.start1();
}
}
@Slf4j(topic = "c.Monitor")
class Monitor extends Thread{
private volatile static Boolean stop = true;
private volatile static Boolean start = false;
@Override
public void run() {
while(!stop){
log.debug("监控中");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("线程已经停止");
start = false;
}
public void start1(){
synchronized (this){
if(start)
return;
log.debug("线程已经开启");
start = true;
stop = false;
new Monitor().start();
}
}
public void stop1(){
stop = true;
}
}
结果:
stop1和start1都需要用volatile修饰,因为停止的线程是唯一的,所以停止只需要保证可见性。不需要加synchronize来保证原子性。