JAVA - 并发之内存模型

JAVA内存模型

image
主存:所有线程共享的数据,如静态变量、成员变量。
工作内存:线程私有的数据,如局部变量。

可见性

线程不会停下来

image

原因

image
image
image

解决办法 - volatile (易变)

示例

image

也可以用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"),也能停止。需要查看源码才知道。

image

可见性 VS 原子性

image
image

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

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

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内运行的。
image
image
image

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

指令重排

image

指令重排的前提

image

分析指令重排的现象

影响有序性。
image
image
也有可能是0,需要大量测试才有可能重现。
image

解决办法

image

volatile原理

image
image
image
image

注意:只需要加在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;
    }

}

图示解释:
image
image

解决办法

用volatile修饰变量

private static volatile Singleton INSTANCE = null;

原理如下图:
image

happens-before

image

线程t1的写入对线程t2的读取是可见的。
image

image

image

image

image

image

image

习题

balking模式练习题

posted @ 2026-02-05 21:33  cdc321  阅读(2)  评论(0)    收藏  举报