并发_02_JAVA并发机制的底层实现原理

一、并发编程中的三个概念

1.1 原子性

  • 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

1.2 可见性

  • 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

1.3 有序性

  • 有序性:即程序执行的顺序按照代码的先后顺序执行

二、Volatile

它是轻量级的synchronized,比之执行成本更低,因为它不会引起线程的上下文切换,它在多处理器开发中保证了共享变量的“可见性”,“可见性”的意思是当一个线程修改一个变量时,另外一个线程能读到这个修改的值。

2.1 volatile关键字的两层语义

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  • 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  • 2)禁止进行指令重排序。

2.2 volatile的使用条件

  • Volatile 变量具有 synchronized 的可见性特性,但是不具备原子性。这就是说线程能够自动发现 volatile 变量的最新值。
    volatile不是线程安全的,单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类

  • Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束
    在某些情况下,如果读操作远远大于写操作,volatile 变量还可以提供优于锁的性能优势

要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
- 对变量的写操作不依赖于当前值。
- 该变量没有包含在具有其他变量的不变式中

2.3 被volatile修饰的变量,在被执行赋值操作时,它使用lock前缀的汇编指令,保证

  • 1,将当前处理器缓存行的数据写回到系统内存,

  • 2,这个写回内存的操作会使其在其他CPU里缓存了该内存地址数据无效,这样其它cpu再想获取到该值时,会重新从内存中读取数据

上面两条规则,就是著名的“缓存一致性原则”

2.4 volatile关键字禁止指令重排序有两层意思

  • 1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

  • 2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

2.5 volatile的原理和实现机制

前面讲述了源于volatile关键字的一些使用,下面我们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。

下面这段话摘自《深入理解Java虚拟机》:

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

2)它会强制将对缓存的修改操作立即写入主存;

3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

三、Synchronized

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。

3.1 Synchronized的作用

主要有三个:

  • 确保线程互斥的访问同步代码
  • 保证共享变量的修改能够及时可见
  • 有效解决重排序问题。

3.2 从语法上讲,Synchronized总共有三种用法

  • 1,修饰普通方法,锁是当前实例对象

  • 2,修饰静态方法,锁是当前类的class对象

  • 3,修饰代码块,锁是synchronized括号里配置的对象

同步代码块是使用monitorenter和monitorexit指令实现的,每个对象都有一个monitor与之相关联,当且仅当它被持有后,它将处于锁定状态

synchronized的锁存储在java对象头里面,锁一共有4中状态,依次是:无锁状态<偏向锁状态<轻量级锁状态<重量级锁状态, 另外随着竞争情况,锁会升级,而且升级后不能降级,这样做是为了保证获得锁和释放锁的效率.

偏向锁: 现实中锁不仅不存在多线程竞争,而且总是被同一个线程获得,为了降低线程获得锁的开销,引入偏向锁。偏向锁的撤销只有当竞争出现的时候才会发生。

轻量级锁:通过CAS自旋的方式,没有获取资源的线程不必阻塞,也就不会有上下文的切换,从而降低了开销,但是CAS自旋也是消耗CPU的,所以轻量级锁使用于同步块执行速度快,锁的持有时间比较短的情况,响应速度比较快

重量级锁:线程之间竞争不存在自旋的情况,直接阻塞,上下文切换,系统开销比较大,适用于同步块所需要的时间比较久的情况,系统吞吐量比较大

3.3 总结

Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到内核态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。

因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“偏向锁”和“轻量级锁”。无锁 –> 偏向锁 –> 轻量级 –> 重量级

四、原子操作的实现原理

4.1 理解几个术语

1,缓存行,cache line,缓存的最小操作单位

2,比较并交换,compare and exchange, CAS操作需要输入两个值,一个old value(也是expected value),一个new value,在操作期间,如果旧值发生了变化,则新值不会交换,这个就是为了解决例如 i++这种情况的,i读取,i++, i被赋值,但是如果多个线程同时访问这个i值,则下一个线程读取到的可能是脏数据,这时候新值就不该被赋给变量,自旋CAS的实现原理就是,当这种情况发生时,放弃赋值,重新尝试,直到成功为止

3,CPU流水线,一条指令被分解成多个步骤,每个步骤在不同的电器元件上执行,类似于车间流水线,这样可以提高效率

4,内存顺序冲突,这个概念我稍微有点疑惑,它是由假共享引起的,是指多个CPU同时修改一个缓存行的不同部分引起其中一个CPU的操作无效,当出现这种情况时,CPU流水线会被清空,这个清空我自己的理解是说,后续指令需要依赖前面指令才能成功,如果前面缓存更新失效了,后续指令需要重新执行

4.2 原子操作实现

处理器如何实现原子操作

1,总线加锁实现原子操作

2,缓存加锁实现原子操作

Java如何实现原子操作呢

1,使用循环CAS实现原子操作

2,使用锁实现原子操作

下面附上一段循环CAS代码

public class AtomicJava {

    private static AtomicInteger atomicI = new AtomicInteger();
    private static int i = 0;

    public void count(){

        /* 自旋CAS(compareAndSet)的基本思路就是循环进行CAS操作直到成功为止
         * atomicI.getAndIncrement();
          public final int getAndIncrement() {
                for (;;) {//
                    int current = get();
                    int next = current + 1;// current和next不存在被竞争的情况,但是get()获取到执行compareAndSet之间,很可能原值已经被其它线程改变了
                    if (compareAndSet(current, next))
                        return current;
                }
            }*/
    }

    public static void main(String[] args){
        final AtomicJava counter=new AtomicJava();
        List<Thread> list=new ArrayList<Thread>();
        for(int i=0;i<100;i++){
            Thread t=new Thread(new Runnable(){
                @Override
                public void run() {
                    for(int j=0;j<1000;j++){
                    counter.safeCount();
                    counter.unSafeCount();
                    }
                }
            });
            list.add(t);

        }
        for(Thread t:list){
            t.start();
        }

        try {
            Thread.sleep(1000);
            for (Thread t : list) {
                t.join();
            }
            System.out.println("Unsafe count: "+i);
            System.out.println("Safe count: "+atomicI.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 使用CAS实现线程安全计数器
     */
    private void safeCount() {
        for (;;) {
            int i = atomicI.get();
            boolean suc = atomicI.compareAndSet(i, ++i);
            if(suc)
                return;
        }
    }

    /**
     * 非线程安全的count
     */
    private void unSafeCount(){
        i++;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值