zoukankan      html  css  js  c++  java
  • java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    前言:如有不正确的地方,还望指正。

    目录

    Synchronized

    原理

    • synchronized关键字是通过字节码指令来实现的
    • synchronized关键字编译后会在同步块前后形成monitorenter和monitorexit两个字节码指令
    • 执行monitorenter指令时需要先获得对象的锁(每个对象有一个监视器锁monitor),如果这个对象没被锁或者当前线程已经获得此锁(也就是重入锁),那么锁的计数器+1。如果获取失败,那么当前线程阻塞,直到锁被对另一个线程释放
    • 执行monitorexit指令时,计数器减一,当为0的时候锁释放
    class Test
    {
        public int i=1;
        public void test()
        {
    	    synchronized (this)
    	    {
    	    	i++;	
    	    }
    }
    }
    
    • 反编译后结果

    volatile

    作用

    • 保证变量对所有的线程的可见性,当一个线程修改了这个变量的值,其他线程可以立即知道这个新值(之所以有可见性的问题,是因为java的内存模型)

    原理

    • 所有变量都存在主内存,每条线程有自己的工作内存,工作内存保存了被该线程使用的变量的主内存副本拷贝
    • 线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存的变量,也就是必须先通过工作内存
    • 一个线程不能访问另一个线程的工作内存
    • volatile保证了变量更新的时候能够立即同步到主内存,使用变量的时候能立即从主内存刷新到工作内存,这样就保证了变量的可见性
    • 实际上是通过内存屏障来实现的。语义上,内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。

    Atomic

    作用

    • 当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以像自旋锁一样,继续尝试,一直等到执行成功。

    原理

    • CAS操作(compare and swap 对比和设置),是通过一个cpu指令实现的,这个指令是一个原子指令,指令有3个操作数ABC,A为内存位置,B为预期值,C为新值,如果A符合旧预期值B,那么用V更新A的值,如果不符合就不更新,这个过程是原子操作
    • 所以我们并没有通过代码来实现同步,而是通过硬件级别的cpu指令来实现的,并不像synchronized一样阻塞线程
    //加一并返回值
    public final int incrementAndGet() {
            for (;;) {
                int current = get();
                int next = current + 1;
                if (compareAndSet(current, next))
                    return next;
            }
       }
    
    //返回CAS操作成功与否
    public final boolean compareAndSet(int expect, int update) {
            //根据变量在内存中的偏移地址valueOffset获取原值,然后和预期值except进行比,如果符合,用update值进行更新,这个过程是原子操作
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }
    
    
     
    
    • 如果此时有两个线程,线程A得到current值为1,线程B得到current值也为2,此时线程A执行CAS操作,成功将值改为2,而此时线程B执行CAS操作,发现此时内存中的值并不是读到current值1,所以返回false,此时线程B继续进行循环,最后成功加1

    Lock

    作用

    • 显式加锁

    原理

    • 通过同步器AQS(AbstractQueuedSynchronized类)来实现的,AQS根本上是通过一个双向队列来实现的
    • 线程构造成一个节点,一个线程先尝试获得锁,如果获取锁失败,就将该线程加到队列尾部
    • 非公平锁的lock方法,调用的sync(NonfairSync和fairSync的父类)的lock方法
     public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    
    // ReentrantLock的lock方法
    public void lock() {
            sync.lock();
        }
    
    • NonfairSync的lock方法,acquire的是Sync的父类AQS的acquire方法
    
    final void lock() {
                //如果当前同步状态为0(锁未被占有),CAS操作设置同步状态,设置成功的话当前线程获得锁(如果此时是公平锁,那么不会执行compareAndSetState方法,直接acuire排队)
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                //否则调用AQS的acquire方法
                else
                    acquire(1);
            }
    
    
     //CAS设置锁的状态
     protected final boolean compareAndSetState(int expect, int update) {
            // See below for intrinsics setup to support this
            return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
        }
    
    • AQS的acquire方法
    //尝试获得锁,如果获取失败,将节点加入到尾节点
    public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
    
    • tryAcquire方法,尝试获得锁
    final boolean nonfairTryAcquire(int acquires) {
        
        final Thread current = Thread.currentThread();
        //获取state变量值
        int c = getState();
        if (c == 0) { //如果没有线程占用锁
            if (compareAndSetState(0, acquires)) {
                //设置当前线程获得锁
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) { //当前线程已经占用该锁
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 更新state值为新的重入次数
            setState(nextc);
            return true;
        }
        //获取锁失败
        return false;
    }
    
    
    • 如果获取锁失败,将节点加入尾节点
    private Node addWaiter(Node mode) {
             Node node = new Node(Thread.currentThread(), mode);
             // Try the fast path of enq; backup to full enq on failure
             Node pred = tail;
                //如果尾节点不为空
             if (pred != null) {
                 node.prev = pred;
                    //此时可能同时有其他线程插入,再进行判断(通过CAS),如果没有,将节点设置为尾节点
                 if (compareAndSetTail(pred, node)) {
                     pred.next = node;
                     return node;
                 }
             }
                //如果节点为空或者节点不为空并且有其他线程插入(CAS返回false),执行enq方法
             enq(node);
             return node;
         }
    
    • 如果节点为空或者节点不为空并且有其他线程插入(CAS返回false),执行enq
    //通过自旋进行设置
    private Node More enq(final Node node) {
             for (;;) {
                 Node t = tail;
                 if (t == null) { // Must initialize
                     if (compareAndSetHead(new Node()))
                         tail = head;
                 } else {
                     node.prev = t;
                     if (compareAndSetTail(t, node)) {
                         t.next = node;
                         return t;
                     }
                 }
             }
         }
    
    • 进入队列的线程尝试获得锁
    
     
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true; //是否成功获取锁
        try {
            boolean interrupted = false; //线程是否被中断过
            for (;;) {
                final Node p = node.predecessor(); //获取前驱节点
                //如果前驱是head尝试获锁
                if (p == head && tryAcquire(arg)) {
                    setHead(node); // 获取成功,将当前节点设置为head节点
                    p.next = null; // 原head节点出队
                    failed = false; 
                    return interrupted; //返回是否被中断过
                }
                // 前节点不是头节点或者获取失败,判断是否可以挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    // 线程若被中断,设置interrupted为true
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
    
    
    • 线程是否可以挂起
    
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //前驱节点的状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            // 前驱节点状态为signal(此节点线程结束后唤醒下一个节点线程)
            return true;
        //如果 前驱节点状态为CANCELLED(线程已经被取消)
        if (ws > 0) {
            // 删除cancelled状态的节点
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 将前驱节点的状态设置为SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    
    • 挂起当前线程,返回线程中断状态并重置
     
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    

    我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

    作者:jiajun 出处: http://www.cnblogs.com/-new/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

  • 相关阅读:
    arcgis api 3.x for js 入门开发系列八聚合效果(附源码下载)
    arcgis api 3.x for js 入门开发系列七图层控制(附源码下载)
    arcgis api 3.x for js 入门开发系列六地图分屏对比(附源码下载)
    arcgis api 3.x for js 入门开发系列五地图态势标绘(附源码下载)
    arcgis api 3.x for js 入门开发系列四地图查询(附源码下载)
    Java里面获取当前服务器的IP地址
    Flutter at Google I/O 2018
    Modbus RTU 协议使用汇总
    plsql 创建表空间、用户、赋予权限
    Oracle:ODP.NET Managed 小试牛刀
  • 原文地址:https://www.cnblogs.com/-new/p/7326820.html
Copyright © 2011-2022 走看看