• java并发编程之美-阅读记录6


    java并发包中锁

    6.1LockSupport工具类

      该类的主要作用就是挂起和唤醒线程,该工具类是创建锁和其他工具类的基础。LockSupport类与每个使用他的线程都关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。

      1、void park()

        如果调用park方法的线程已经那都了LockSupport关联的许可证的话,那LockSupport.park()会立刻返回,否则就会阻塞挂起。

    package com.nxz.blog.otherTest;
    
    import java.util.concurrent.locks.LockSupport;
    
    public class TestThread004 {
    
        /**
         * LockSupport park
         * @param args
         */
        public static void main(String[] args) {
            System.out.println("main-start");
            // LockSupport的park默认是不持有许可证的,也就是说,调用park方法后,当前线程会阻塞
            LockSupport.park();
            System.out.println("main-end");
        }
    }
        

    上边运行结果:如下图,main线程会阻塞在LockSupport.park()代码处,不会输出main-end。

      2、void unpark(Thread thread)方法

        如果参数thread没有持有LockSupport许可,调用该方法后,会使thread持有许可证,也就是说会使调用park方法而阻塞的线程返回。(线程intercept中断之后,park方法也会返回,停止阻塞)

    package com.nxz.blog.otherTest;
    
    import java.util.concurrent.locks.LockSupport;
    
    public class TestThread004 {
    
        /**
         * LockSupport park
         * @param args
         */
        public static void main(String[] args) {
            System.out.println("main-start");
            // 使当前线程(main线程)获取许可
            LockSupport.unpark(Thread.currentThread());
            // 因为上边已经获取许可了,所以,下边这个park方法并不会阻塞线程
            LockSupport.park();
            System.out.println("main-end");
        }
    }

    执行结果:

    main-start
    main-end

    另外一个例子:

    package com.nxz.blog.otherTest;
    
    import java.util.concurrent.locks.LockSupport;
    
    public class TestThread004 {
    
        /**
         * LockSupport park
         *
         * @param args
         */
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("runnable-start");
                    LockSupport.park();
                    System.out.println("runnable-end");
                }
            });
    
            t.start();
            // 目的是使t线程先执行,让t线程调用park方法后阻塞
            Thread.sleep(1000);
            System.out.println("main");
            // 使t线程获取LockSupport许可,获取许可后,t线程就可以继续向下执行了
            LockSupport.unpark(t);
            System.out.println("main-end");
        }
    }

    执行结果:

    runnable-start
    main
    main-end
    runnable-end

      3、void park(long nanos)

        该方法和park方法类似,只不过是在指定时间后自动返回

      4、void park(Object blocker)

        一般使用的是这个方法而不是无参的park方法,原因是,这个个方法输出日志时会输出阻塞的类的信息(而park方法不会输出)。

    6.2抽象同步类AQS

      AbstractQueuedSynchronize抽象同步队列简称AQS,是实现同步器的基础组件,并发包中锁的实现,底层都是通过AQS实现的。

      1、基本属相

       // 同步器是一个双向的FIFO队列  有头结点和尾节点,节点类型Node为AQS的内部类
        private transient volatile Node head;
    
        private transient volatile Node tail;
        // 该字段是实现锁和同步器的关键,在不同的实现类中有不同的含义,例如在ReentrantLock中代表当前线程获取可重入锁的次数,ReentrantReadWriteLock中,高16位表示读状态,也就是获取读锁的次数,低16位掉表写状态,也及时写锁的次数,Semaphore中代表限号量等等
        private volatile int state;
    static final class Node {
            // 用来标记该线程是获取共享资源时被阻塞后挂起放入AQS队列的
            static final Node SHARED = new Node();
            // 用来标记该线程是获取独占资源师被阻塞后防区AQS队列的
            static final Node EXCLUSIVE = null;
    
            // waitstatus状态之一, 表示线程被取消了
            static final int CANCELLED =  1;
            //waitstatus状态之一,表名线程需要唤醒
            static final int SIGNAL    = -1;
            // 线程在条件队列里边等待 
            static final int CONDITION = -2;
            // 释放共享资源师需要通知其他节点
            static final int PROPAGATE = -3;
    
            // 记录当前线程的等待状态,有以上3中状态
            volatile int waitStatus;
    
            // 记录当前节点的前驱节点
            volatile Node prev;
    
            // 记录当前节点的后继节点
            volatile Node next;
    
            // 记录当前线程
            volatile Thread thread;
    
            // 下一个等待条件变量condition的节点
            Node nextWaiter;
        public class ConditionObject implements Condition, java.io.Serializable {
           // 该类用来结合所实现线程同步的,每一个ContditionObject是一个条件变量,每一个条件变量对应一个条件对列,每一个条件队列都是一个单项链表,用来存放调用await方法后阻塞的线程
           // 条件队列的第一个节点
            private transient Node firstWaiter;
            // 条件队列的最后一个节点
            private transient Node lastWaiter;
    }

    6.3ReentrantLock可重入的独占锁

      1、结构图:

      

      可以看出ReentrantLock最终还是通过AQS实现的,并根据参数判断锁是公平的还是非公平的

        // 默认构造是创建一个非公平锁
        public ReentrantLock() {
            sync = new NonfairSync();
        }
    
        // 有参构造,fair:true则创建一个公平锁,false:创建非公平锁
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }

      2、void lock()

        public void lock() {
            sync.lock();
        }
    
        // Sync类中为抽象方法,具体实现,需要看公平锁和非公平锁中的实现方法
        abstract void lock();
    
        // 非公平锁类
        static final class NonfairSync extends Sync {
    
            // lock实现方法
            final void lock() {
    // 通过CAS操作state变量,state默认为0,表名没有被线程获取,设置为1成功后,代表该线程获取锁成功,此时state为1,并设置exclusiveThread为当前线程
    if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else // 调用AQS的acquire方法,AQS内部会条用tryAcquire方法 acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } // 公平锁 static final class FairSync extends Sync { final void lock() { acquire(1); } protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }

      3、lockInterruptibly()方法,可中断的lock方法

        和lock类似,区别就是能够对中断进行相应(而lock方法对于中断操作是忽视的)

      4、trylock()方法

        如果当前锁没有被其他线程持有,则调用该方法时会立即返回,如果被其他线程持有,则该方法也会立即返回false。(该方法不会阻塞,lock方法会阻塞,即会进入阻塞队列中)。

        public boolean tryLock() {
            return sync.nonfairTryAcquire(1);
        }
    
         final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                // 如果state为0,即该锁没有被其他线程持有,则该线程通过CAS操作后,持有锁,会理解返回true
                if (c == 0) {
                    if (compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                // 如果持有锁的线程是当前线程,则state累计额acquires后,返回true
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                // 返回false,在锁被其他线程持有时会立即返回false
                return false;
            }

      5、释放锁unlock()

        如果所被当前线程持有,则state赋为0,即释放锁,如果所被当前线程多次持有,则state只是减1,并不会释放锁。如果当前线程没有持有锁,则跑异常。

    6.4ReentrantReadWriteLock读写锁

      采用读写分离的策略,允许多个线程可以同时获取锁。

      1、结构:有两个锁,WriteLock和ReadLock

      

    public class ReentrantReadWriteLock
            implements ReadWriteLock, java.io.Serializable {
        // 读锁 
        private final ReentrantReadWriteLock.ReadLock readerLock;
        // 写锁 独占锁
        private final ReentrantReadWriteLock.WriteLock writerLock;
        // 同步时 继承自AQS类
        final Sync sync;
    
        public ReentrantReadWriteLock() {
            this(false);
        }
    
        public ReentrantReadWriteLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
            readerLock = new ReadLock(this);
            writerLock = new WriteLock(this);
        }
    
        public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
        public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }
    }

    ReentrantReadWriteLock和ReentrantLock类似,只不过内部分为写锁和读锁,对于aqs中state变量的控制,在ReentrantLock中,0表示未被线程获取,而在读写锁中,将state分成两份,高16位负责记录读锁和低16位负责写锁。

     6.5jdk8中新增的StampedLock锁

      该锁是jdk8中新增的,提供了3中模式的读写控制,当调用获取锁的函数时,会返回一个long类型的变量,也就是戳记(stamp),代表锁的状态。当调用释放锁和转换锁的时候,需要将该stamp作为参数传入。

      写锁writeLock:是一个独占锁,同一时间只能有一个线程可以获取锁(并且是不可冲入锁)

      悲观读锁readLock:是一个共享锁,在没有线程获取的情况下多个线程可以获取到锁,但是只要有线程获取到写锁,则获取读锁的线程都会阻塞(同时该锁也是不可冲入锁)

      乐观读锁tryOptimisticRead

      使用案例:

    /**
         * jdk8中stampedLock中提供的例子
         * 管理二维点的类
         */
        class Point {
            private double x, y;
            private final StampedLock sl = new StampedLock();
    
            /**
             * 独占的方法
             */
            void move(double deltaX, double deltaY) { // an exclusively locked method
                // 获取写锁
                long stamp = sl.writeLock();
                // x y坐标调整
                try {
                    x += deltaX;
                    y += deltaY;
                } finally {
                    // 释放写锁
                    sl.unlockWrite(stamp);
                }
            }
    
            /**
             * 共享方法,使用了乐观的共享锁
             */
            double distanceFromOrigin() { // A read-only method
                // 获取乐观的读锁
                long stamp = sl.tryOptimisticRead();
                // 获取point对象坐标的拷贝
                double currentX = x, currentY = y;
                // 验证stamp(也就是之前获取的锁是否仍然可用),如果可用的话,则直接进行运算,不可用的话,则获取一个悲观的读锁readlock
                if (!sl.validate(stamp)) {
                    // 在stamp不可用情况下,重新获取一个悲观读锁
                    stamp = sl.readLock();
                    try {
                        // 重新设置xy的拷贝
                        currentX = x;
                        currentY = y;
                    } finally {
                        // 释放悲观读锁
                        sl.unlockRead(stamp);
                    }
                }
                // 返回两点之间的距离
                return Math.sqrt(currentX * currentX + currentY * currentY);
            }
    
            // 更原点
            void moveIfAtOrigin(double newX, double newY) { // upgrade
                // Could instead start with optimistic, not read mode
                long stamp = sl.readLock();
                try {
                    // 如果x=y=0是,修改坐标
                    while (x == 0.0 && y == 0.0) {
                        // 将之前获取到的读锁转换为一个写锁
                        long ws = sl.tryConvertToWriteLock(stamp);
                        //ws不等于0,则代表锁转换成功
                        if (ws != 0L) {
                            stamp = ws;
                            x = newX;
                            y = newY;
                            break;
                        } else {
                            // 转换失败后,释放读锁,重新获取一个写锁,重复while循环
                            sl.unlockRead(stamp);
                            stamp = sl.writeLock();
                        }
                    }
                } finally {
                    // 释放锁
                    sl.unlock(stamp);
                }
            }
        }

    stampedlock和ReentrantReadWriteLock类似,只不过前者是不可重入锁,但是前者在提供的乐观读锁在多线程环境下提供了更好的性能,这是因为乐观读锁不需要进行CAS操作设置锁的状态,只是简单的验证了一下锁的stamp是否可用。

  • 相关阅读:
    io流(input output)
    Mybatis实例
    框架(mybatis概念及环境搭建
    easyui试做窗口.不完整
    while (rs.next()) 与 if(rs.next())的区别
    json名词解释
    jQuery2
    去除inline-block元素间间距的N种方法
    jQuery的deferred对象详解
    js基础知识集锦
  • 原文地址:https://www.cnblogs.com/nxzblogs/p/11332870.html
Copyright © 2020-2023  润新知