1. 变量分为哪几类
    全局变量 = 属性(静态的、非静态的) 局部变量 = 本地变量、参数
  2. 多线程间共享数据
    全局变量:静态变量或共享对象
  3. 并发线程能不能看到共享变量的最新值,这就是并发中变量可见性问题
    (1) 为什么不可见?
    (2) 怎样才能可见
    使用synchroized关键字,对线程主体进行包装
    使用volatile关键字修饰共享变量
  4. JAVA内存模型及操作规范
    (1) 共享变量必须存放在主内存。
    (2) 线程有自己的工作内存,线程只可操作自己的工作内存。
    (3) 线程要操作共享变量,需从主内存中读取变量值到工作内存,改变后再从工作内存同步到主内存。


5. JAVA内存模型带来的问题

(1) 数据存在多份拷贝,导致多线程同时读写同一变量时,存在数据不准确的问题。即线程安全问题。

(2) 所以要使用线程同步或锁处理。

  1. 变量在线程1中更改,在线程2中能看到该变量的最新值
    (1) 线程1修改A后,必须马上同步回主内存
    (2) 线程2使用A前,需要重新从主内存读取到工作内存
  2. JAVA内存模型-同步交互协议,规定了8种原子操作
    (1) lock(锁定) 将主内存中的变量锁定,为一个线程所独占。
    (2) unlock(解锁) 将lock加的锁解除,允许其它线程访问主内存中的该变量。
    (3) read(读取) 作用于主内存变量,将主内存中的变量值读取到工作内存(从主内存读取到寄存器)。
    (4) load(载入) 作用于工作内存变量,将read读取到的值保存到工作内存中的变量副本中(寄存器变量载入到工作内存)。
    (5) use(使用) 作用于工作内存变量,将值传递给线程的代码执行引擎。
    (6) assign(赋值) 作用于工作内存变量,将执行引擎处理返回的值重新赋值给变量副本。
    (7) store(存储) 作用于工作内存变量,将变量副本的值传送到主内存中(从工作内存读取到寄存器)。
    (8) write(写入) 作用于主内存变量,将store传入的值写入到主内存的共享变量中(寄存器变量写入到主内存)。
    注意:任意间的组合操作,不是原子的。
    (1) 将一根变量从主内存复制到工作内存,要顺序执行read、load操作;要将变量从工作内存同步回主内存要顺序执行stroe、write操作。只要求顺序执行,不一定是连续执行(即原子操作)。
    (2) 做了assign操作,必须同步回主内存。不能没做assign操作,同步回主内存。(不一定assign后,马上同步回主内存)
  3. 并发中保证变量可见性方法 final synchronized volatile
  4. synchronized语义规范(既可以保证可见性,又可以保证线程安全)
    (1) 进入同步块前,先清空工作内存中的共享变量,从主内存中重新加载。
    (2) 解锁前,必须把修改的共享变量同步回主内存。
    (3) 最核心的,是synchronized有锁机制,只有获得锁的线程才能操作共享资源。(悲观锁)



  1. volatile语义规范(保证可见性,保证工作内存的变量和主内存的变量的值一致)
    (1) 使用volatile变量时,必须重新从主内存加载,并且read、load是连续的。
    (2) 修改volatile变量后,必须马上同步回主内存,并且store、write是连续的。
  2. volatile能做到线程安全吗?(不能保证原子性)
    (1) 不能,因为它没有锁机制,线程可并发操作共享资源。



  1. volatile比synchronized,使用简单、性能稍好。
  2. volatile还可以用于限制局部代码指令重排序

(1) 线程A和线程B的部分代码

线程A
    content = initContent();
    
    isInit = true;
复制代码
线程B
    while(isinit) {
        content.oper();
    }
复制代码

(2) jvm优化指令重排序后

线程A(因为两行代码,没有任何关系)
    isInit = true;
    content = initContent();
复制代码

(3) 当两个线程并发执行时,就可能出现线程B中content发生空异常。

  1. volatile的使用范围
    (1) 只可修饰成员变量(静态、非静态)。因为用来保证共享变量可见性,共享变量只能是全局变量。
    (2) 多线程并发下,才需要使用。
  2. 单例模式中对volatile关键字的使用