Java 内存模型

1. 内存模型概念多线程

(1)内存模型(Java Memory Model)和内存结构(堆栈那些)不是一个层面的概念,JMM 定义了一套在多线程读写共享数据(成员变量,静态变量等,而不是局部变量这种线程私有的)时,对数据的可见性、有序性、和原子性的规则和保障。并发

(2)JMM规定了全部的变量都存储在主内存(虚拟机内存的一部分,但能够和操做系统的类比)中;ide

每条线程还有本身的工做内存,线程的工做内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的全部操做(读取,赋值)都必须在工做内存中进行,而不能直接读写主内存中的变量;优化

不一样的线程之间也没法直接访问对方的工做内存中的变量,线程间变量的值传递均须要经过主内存来完成。操作系统

(3)若是非要把主内存工做内存,和内存结构的堆栈方法区对应,那么主内存应该对应堆中的对象的实例数据部分,而工做内存对应栈。线程

 

2. 原子性对象

要执行就执行完,不能执行一半blog

违背原子性例子:两个线程对一个静态变量 i=0 执行 i++ 和 i--(每一个对应四条JVM字节码指令),可能致使结果不是0内存

解决:用synchronized加锁,加锁位置应尽可能减小代码获取释放锁的次数编译器

synchronized( 对象 ) {
    要做为原子操做代码
}

 

3. 可见性

指一个对象能看到或访问另外一个对象的能力

违背可见性的例子:t线程看不到主线程run变量的改变

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{while(run){// ....        }
    });
    t.start();
    Thread.sleep(1000);
    run = false; // 线程t不会如预想的停下来}

解决:volatile(易变关键字),它能够用来修饰成员变量和静态成员变量,线程操做 volatile 变量都是直接操做主存

所以上面的run被修改后会存到主内存,t线程访问主内存内容会看到改变

 

4. 有序性

代码按顺序执行

违背有序性的例子:因为即时编译器在运行时会有指令重排的优化,多线程状况下可能出现非预期结果

解决:volatile 修饰的变量,能够禁用指令重排

所以volatile能够保证可见性和有序性,不能保证原子性,但属于轻量级并发控制;而synchronized能够保证三者,但更重量级

 

5. CAS

CAS 即 Compare and Swap ,它的实现用的是乐观锁的思想

// 须要不断尝试while(true) {int 旧值 = 共享变量 ; // 好比拿到了当前值 0int 结果 = 旧值 + 1; // 在旧值 0 的基础上增长 1 ,正确结果是 1if( compareAndSwap ( 旧值, 结果 )) {// 成功,退出循环    }
}
  • 获取共享变量时,为了保证该变量的可见性,须要使用 volatile 修饰
  • 结合 CAS 配合 volatile 能够实现无锁并发,适用于竞争不激烈、多核 CPU 的场景下
  • 由于没有使用 synchronized(悲观锁),因此线程不会陷入阻塞,这是效率提高的因素之一
  • 但若是竞争激烈,能够想到重试必然频繁发生,反而效率会受影响
  • CAS 底层依赖于一个 Unsafe 类来直接调用操做系统底层的 CAS 指令
  • 原子操做类例如:AtomicInteger、AtomicBoolean等,它们底层就是采用 CAS 技术 + volatile 来实现的
相关文章
相关标签/搜索