多线程并发之底层原理

并发原理、Java 内存模型 (JMM)

image

线程共享变量存储在主内存中,每个线程都有一个本地的私有内存,本地内存中存储着该线程以读或写共享变量的副本,本地内存是一个抽象概念,它涵盖了缓存、写缓冲区、cpu寄存器

线程要读取一个共享变量,会先将其从主内存中读取到本地内存,然后进行运算,最后在将共享变量写回主内存

并发产生的原因
原因:

1.操作的非原子性

2.多个线程之间的内存不可见性

解决:
  • volatile:多线程内存可见性,对单个变量的读或写操作是原子性的
  • CAS: 对单个变量的 读-改-写 操作原子性
  • synchronize: 对同步区域的代码具有原子性和可见性

一般情况下 CAS 都是和 volatile 一起使用的,这样既保证了变量的修改的操作的原子性,又保证了变量的可见性。Java 中 Lock 还有原子类的实现就是基于 CAS 和 volatile

volatile

内存语义:

  • 读写具有原子性:对任意单个volatile变量的读或写具有原子性,但是对于 i++ 这样的符合操作是不具有原子性的
  • 禁止指令重排序:利用内存屏障来禁止volatile 前后的指令重新排序
  • 及时刷新内存:把缓冲区的数据刷新到主内存中,并且使其他线程的缓存区的数据无效(这样其他线程在操作变量时会重新从主内存拉取新数据)
指令重排序

JVM 指令重排是为了优化执行速度,在单线程下指令重排不会影响到程序的执行结果,因为具有关联关系的指令是不会被重排的,但是在多线程下指令重排就不保证最终结果的正确性了

例如:当如果线程A指令重排,2 先于 1 执行,然后线程B就会进入if方法,但是此时 a 还未赋值,就会出现 i 的结果为 0;

class ReorderExample{
    int a = 0;
    boolean flag = false;
    
    //线程A执行该方法
    public void writer(){
        a = 1;  ----------- 1
        flag = true;------- 2
    }
    //线程B执行该方法
    public void reader(){
        if(flag){ ----------3
            int i = a*a; ---4
        }
    }
}

读写原子性和立即刷新内存

因为读或写的操作具有原子性,所以同一时间只会有一个线程对单个 volatile 进行读或写,并且写完后立刻刷新到主内存,这样的话其他线程无论何时访问主内存获取到的都是最新的值

CAS(compareAndSwap)

实现原理:底层指令

//获取 Unsafe
private static final Unsafe unsafe = Unsafe.getUnsafe();

//该方法为 native 方法,在 Unsafe 类里面
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

先读取变量的值判断该变量是否是被其他线程修改过,如果没有修改就更新,并且返回true,如果发现变量被修改了则返回false,开发者可以根据返回结果进行自旋重试。

此操作具有volatile 读和写的内存语义,即对单个变量读写的原子性,正是因为这样才能正确的读取到内存中的值,从而进行判断内存中的值和当前的预期的值是否一致。

synchronized

被称之为重量级锁,1.6 对其进行了优化,引入偏向锁和轻量级锁,使其不是那么重了。

实现原理:

synchronized 的锁是存储在对象头中的,

将锁放入对象头中,每个线程利用CAS争抢锁,在代码执行进入到同步块的时候获取锁,结束时释放锁,如果没有锁竞争的情况下只有一个线程,还是会执行获得锁和释放锁这样的操作。

java1.6 为了减少获得锁和释放锁带来的性能消耗,在没有锁竞争的时候使用 偏向锁 ,获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出 同步块时不需要进行CAS操作来加锁和解锁。

当出现锁竞争时,会升级为 轻量级锁

如果在轻量级锁竞争时失败了,会升级为 重量级锁

CAS、volatile、synchronized 的优缺点:

  • volatile 和 CAS 只能实现单个变量的读或写的安全性,但是如果是像更新变量这种操作包含了三步操(读-运算-写),这种情况 volatile 已经无法保证线程安全了。原因是这个操作是非原子操作
  • CAS 存在ABA问题,重试机制会一直消耗cpu资源
  • synchronized 可以对多个步骤实现线程安全操作,性能不如 volatile 和 CAS

下面演示了,普通变量、volatile变量和Atomic变量的原子性,其中 Atomic 类的底层实现就是利用 CAS+重试

public class AtomicTest {

    /**使用 volatile 修饰不能保证 volatileCount = volatileCount + 1 线程安全;*/
    private volatile static int volatileCount;
    private static int count;

    private static AtomicInteger atomicCount = new AtomicInteger();

    public static void main(String[] args){

        ExecutorService executorService = Executors.newCachedThreadPool();

        for(int i=0;i<1000;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    //非原子性操作
                    count++;
                    //原子性操作
                    atomicCount.getAndIncrement();
                    //非原子性操作
                    volatileCount = volatileCount + 1;
                }
            });
        }

        //等待上面任务执行完成
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //打印出线程池信息,检查线程是否执行完了
        println(executorService.toString());
        //volatileCount 和 count 的值有可能小于 1000
        println("count= "+count);
        println("atomicCount= "+atomicCount.get());
        println("volatileCount= "+volatileCount);
    }
}

说明 volatile 不能保证复合运算的安全性
class VolatileFeaturesExample { 
    volatile long vl = 0L; // 使用volatile声明64位的long型变量 public void set(long l) { 
        vl = l; // 单个volatile变量的写 
    }
    public void getAndIncrement () { 
        vl++; //复合(多个)volatile变量的读/写
    }
    public long get() {
        return vl; // 单个volatile变量的读
    } 
}

class VolatileFeaturesExample { 
    long vl = 0L; // 64位的long型普通变量
    public synchronized void set(long l) {// 对单个的普通变量的写用同一个锁同步 
        vl = l; 
    }
    public void getAndIncrement () {
        // 普通方法调用 
        long temp = get(); //调用已同步的读方法 
        temp += 1L; // 普通写操作 
        set(temp); // 调用已同步的写方法 
    }
    public synchronized long get() { 
        // 对单个的普通变量的读用同一个锁同步 return vl; 
    } 
    
}

你可能感兴趣的:(多线程并发之底层原理)