JAVA - 并发之内存模型
JAVA内存模型

主存:所有线程共享的数据,如静态变量、成员变量。
工作内存:线程私有的数据,如局部变量。
可见性
线程不会停下来

原因



解决办法 - volatile (易变)
示例

也可以用synchronized方式
@Slf4j(topic = "c.test33")
public class Test33 {
static final Object object =new Object();
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while (true){
synchronized (object){
if (!run){
break;
}
}
}
},"t1");
t.start();
Thread.sleep(1000);
log.debug("停止 t1");
synchronized (object){
run=false;
}
}
}
有空留意,System.out.println("aaaaaaa")或者log.debug("aaaaaaaa"),也能停止。需要查看源码才知道。

可见性 VS 原子性


volatile之两阶段终止模式
示例代码
@Slf4j(topic = "c.TwoInterrupt")
public class TwoInterruptVolatile {
public static void main(String[] args) throws InterruptedException {
TwoInterruptMonitorVolatile twoInterruptMonitorVolatile =new TwoInterruptMonitorVolatile();
twoInterruptMonitorVolatile.start();
//监控3.5秒
Thread.sleep(3500);
twoInterruptMonitorVolatile.stop();
}
}
@Slf4j(topic = "c.TwoInterruptMonitor")
class TwoInterruptMonitorVolatile {
private Thread monitor;
private volatile boolean stop = false;
public void start(){
monitor= new Thread(() -> {
Thread thread = Thread.currentThread();
while (true){
if (stop){
log.debug("处理后事");
break;
}
try {
//每隔1秒监控一次
Thread.sleep(1000);
log.debug("执行监控");//情况1:此处被打断
} catch (InterruptedException e) {
log.debug("被打断停止监控");
//不用重新设置打断标记位
//thread.interrupt();
}
}
},"monitor");
monitor.start();
}
public void stop(){
stop=true;
//这样可以立刻打断,线程monitor每隔1秒监控一次,上面的测试方法中是3.5秒,如果没有立刻打断那么会4秒后停止,因为线程monitor要等待完1秒,立刻打断3.5秒后停止
monitor.interrupt();
}
}
输出结果:可以看到,有monitor.interrupt(),3.5秒后就停止了,没有就要4秒。
10:12:30.601 [monitor] c.TwoInterruptMonitor - 执行监控
10:12:31.604 [monitor] c.TwoInterruptMonitor - 执行监控
10:12:32.605 [monitor] c.TwoInterruptMonitor - 执行监控
10:12:33.105 [monitor] c.TwoInterruptMonitor - 被打断停止监控
10:12:33.106 [monitor] c.TwoInterruptMonitor - 处理后事
volatile - 同步模式之Balking

上面的两阶段终止,监控类TwoInterruptMonitorVolatile是用来监控的,应该是生成一个对象,如果start方法被调用两次,会生成两个监控对象,避免这种情况,可以采用同步模式之Balking
比如会有如下的结果:

Balking示例代码
@Slf4j(topic = "c.TwoInterrupt")
public class TwoInterruptBalking {
public static void main(String[] args) throws InterruptedException {
TwoInterruptMonitorBalking twoInterruptMonitorBalking =new TwoInterruptMonitorBalking();
twoInterruptMonitorBalking.start();
twoInterruptMonitorBalking.start();
Thread.sleep(5000);
twoInterruptMonitorBalking.stop();
}
}
@Slf4j(topic = "c.TwoInterruptMonitor")
class TwoInterruptMonitorBalking {
private Thread monitor;
//停止标记
private volatile boolean stop = false;
//判断是否创建过对象,不需要加上volatile
private boolean starting = false;
public void start(){
synchronized (this){
if (starting){
return;
}
starting=true;
}
//创建对象的方法不必放在同步代码块中,避免影响性能,不放在同步中,也可以保证原子性。
monitor= new Thread(() -> {
Thread thread = Thread.currentThread();
while (true){
if (stop){
log.debug("处理后事");
break;
}
try {
//每隔1秒监控一次
Thread.sleep(1000);
log.debug("执行监控");//情况1:此处被打断
} catch (InterruptedException e) {
log.debug("被打断停止监控");
//不用重新设置打断标记位
//thread.interrupt();
}
}
},"monitor");
monitor.start();
}
public void stop(){
stop=true;
//这样可以立刻打断,线程monitor每隔1秒监控一次,上面的测试方法中是3.5秒,如果没有立刻打断那么会4秒后停止,因为线程monitor要等待完1秒,立刻打断3.5秒后停止
monitor.interrupt();
}
}
输出如下:
10:44:14.864 [monitor] c.TwoInterruptMonitor - 执行监控
10:44:15.877 [monitor] c.TwoInterruptMonitor - 执行监控
10:44:16.879 [monitor] c.TwoInterruptMonitor - 执行监控
10:44:17.885 [monitor] c.TwoInterruptMonitor - 执行监控
10:44:18.860 [monitor] c.TwoInterruptMonitor - 被打断停止监控
10:44:18.860 [monitor] c.TwoInterruptMonitor - 处理后事
Balking之应用
说明:web应用有个监控功能,用户点击开始监控内存,可以点击停止。为了保证多次点击只创建一个监控对象,需要对starting加上volatile。@Service模式是单例的,starting=false,这段不在同步代码块内,点击开始,tomcat会有一个线程对应一个请求,然后这个线程里面又启动了一个线程,要保证可见,所以需要加。上面的示例代码中没加,是因为都在主线程main内运行的。



指令重排引起的问题与解决
指令重排

指令重排的前提

分析指令重排的现象
影响有序性。


也有可能是0,需要大量测试才有可能重现。

解决办法

volatile原理




注意:只需要加在ready之前,num不用加,因为是ready之前的代码涉及的变量都是屏障的
设计模式 - 单例模式之双重锁校验
示例代码一
影响性能,每次都需要加类锁
public final class Singleton {
private Singleton(){ }
private static Singleton INSTANCE = null;
public static synchronized Singleton getInstance(){
if (INSTANCE==null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
改进代码
然而这段代码是有问题的,由于指令重排,可能会出现这种情况,线程1,new Singleton()还没执行完(构造代码里可能有很多代码要执行),jvm就先把该对象引用赋值给INSTANCE,线程2判断不会空,直接返回,导致后面使用该对象时出错。
现在这里是出现了有序性问题,synchronized虽然能保证有序性、原子性、可见性,但是INSTANCE不是完全在synchronized代码块内,有个if (INSTANCE == null)的判断在外面,所以保证不了有序。
public final class Singleton {
private Singleton (){ }
private static Singleton INSTANCE = null;
public static Singleton getInstance(){
if (INSTANCE == null){//判断要不要加锁同步
synchronized (Boolean.class){//线程2
if (INSTANCE==null){//线程1
INSTANCE= new Singleton ();
}
}
}
return INSTANCE;
}
}
图示解释:


解决办法
用volatile修饰变量
private static volatile Singleton INSTANCE = null;
原理如下图:

happens-before
线程t1的写入对线程t2的读取是可见的。








浙公网安备 33010602011771号