jvm内存模型与原子性,可见性,有序性

一.内存模型:

  1. 每一个线程有一个工作内存,和主存是独立的。 
  2. 工作内存存放主存重变量的值得拷贝。
  3. 线程独享的工作内存和主存的关系,如下图:

       

  1. 当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作; 
  2. 当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作;
  3. 每一个操作都是原子的,即执行期间不会被中断,即read不会中断,但是read和load直接会有中断。 
  4. 对于普通变量,一个线程中更新的值,不能马上反应在其他变量中。

如果需要在其他线程中立即可见,需要使用 volatile 关键字

jmm控制共享变量和共享变量副本直接的拷贝,基于cpu优化的原因,会有一定程度的延迟。

 

二.原子性:

原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。

i++是原子操作吗?不是。i++由三个原子操作组成:1.线程私有内存从主存把i的值拷贝下来。2.执行i++操作。3.把i的值赋给主存。

三.可见性:

可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。

1. 编译器优化  2.硬件优化(如写吸收,批操作)

这幅图展示了发生可见性问题的一种可能。如果在CPU1和CPU2
volatile:
volatile修饰的变量,在主内存和线程私有内存直接拷贝不会有延迟。

package com.test.Thread;

import org.apache.logger.log4j.LogManager;
import org.apache.logger.log4j.Logger;

public class VolatileThread extends Thread{
    private static Logger logger = LogManager.getLogger(VolatileThread.class);
    private volatile boolean stop = false;
    public void stopMe(){ stop = true;}
    public void run(){
        int i = 0;
        while(!stop){
            i++;
        }
        logger.info("Stop thread");
    }
    
    public void main(String[] args) throws InterruptedException{
        VolatileThread t = new VolatileThread();
        t.start(); //子线程
        Thread.sleep(1000);
        t.stopMe(); //主线程
        Thread.sleep(1000);
    }
}

volatile 不能代替锁· 一般认为volatile比锁性能好(不绝对) · 选择使用volatile的条件是:语义是否满足应用。

· 可见性:一个线程修改了变量,其他线程可以立即知道。

· 保证可见性的方法:
1.volatile 
2.synchronized(unlock之前,写变量值回主存) 
3.final(一旦初始化完成,其他线程就可见)。 

四.有序性:

在并发时,程序的执行可能会出现乱序。

1.在本线程内,操作都是有序的。(不会破坏语义,所以看似有序)

2.在线程外观察,操作都是无序的。(造成原因:1.指令重排。2.主内存同步延迟--可见性)

class OrderExample{
    int a = 0;
    boolean a = 0;
    
    public void writer(){
        a = 1;
        flag = true;
    }
    
    public void reader(){
        if(flag){
            ...
        }
    }
}

线程A首先执行writer()方法。线程B线程接着执行reader()方法。线程B在int i=a+1 是不一定能看到a已经被赋值为1

因为在writer中,两句话顺序可能打乱:
线程A
flag=true
a=1
线程B
flag=true;在a = 1;之前执行。(此时a=0) 就是说flag=true;和a = 1;这两行很难预料谁先执行。

如何保证有序呢:加synchronized同步,同步后,即使做了writer重排,因为互斥的缘故,reader 线程看writer线程也是顺序执行的。

class OrderExample{
    int a = 0;
    boolean flag = false;

    public synchronized void writer(){
        a = 1;
        flag = true;
    }

    public synchronized void reader(){
        if(flag){
            int i = a+1;
            ...
        }
    }
}

指令重排:编译器为了使性能优化,编译器会将代码指令的顺序进行调整。

保证线程内串行语义,不保证多线程直接的语义。

如:
写后读 a=1;b=a; 写一个变量后,再读这个位置。
写后写 a=1;a=2; 写一个变量后,再写这个变量
读后写 a=b;b=1; 读一个变量后,再读这个变量。
以上语句不能重排。
编译器不考虑多线程间的语义。
可重拍:a=1;b=2; 

指令重排的基本原则:

  • 程序顺序原则:一个线程内保证语义的串行性。
  • volatile规则:volatile变量的写,先发生于读。
  • 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前。
  • 传递性:A指令先于B指令,B指令先于C指令 那么A指令必然先于C指令 。
  • 线程的start方法先于它的每一个动作。
  • 线程的所有操作先于线程的终结(Thread.join())。
  • 线程的中断(interrupt())先于被中断线程的代码。
  • 对象的构造函数执行结束先于finalize()方法 。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值