Java并发工具类(JUC)汇总(一)锁

一、锁

1.锁分类


 在java中,锁按不同的条件及状态可以划分为不同种类的锁,对锁进行细分的主要原因是,给出不同场景下性能及安全性的实践方案,根据实际情况选择最适合的锁。

 

在这里插入图片描述

 

2.无锁技术

2.1 比较与交换(Compare And Swap)


 CAS时是指对某一个变量进行修改的算法,该算法包含三个要素,值所在内存地址(V)预期原值(A)新值(B)。即修改的线程期望在内存地址V中的值是A,如果是A没有变化,更新为新值B,如果不为A有变化时,返回A变化后的值,不更新。
 在使用CAS时,有如下问题需要注意:

  • ABA问题
    如果一个值修改为A,又修改为了B,最后又修改回了A,对于执行修改的线程,预期原值仍然是A,认为这个变量没有被修改过,但实际已经被修改过了。解决该问题应使用版本号,更改变量后版本号+1,在CAS的基础上,多比对一下版本号是否变化。
  • 当循环时间长时,CPU的开销大
    由于CAS使用自旋的方式一直尝试设置新的值,设置失败继续失败,如果一直不成功,将一直占用CPU的时间,造成很多无用的开销
  • 只能保证一个共享变量的操作
    CAS并不能保证原子性,所以如果涉及多个共享变量的操作需要进行加锁,或者直接将多个共享变量和为一个变量(锁实现源码中就是用的该方式),又或者将多个共享变量封装为一个对象进行设置(AtomicReference)

 

2.2 写入时复制 (Copy-on-write)


 写入时复制是指,在写入时复制一份共享变量的副本,在副本中进行写入,待写入完成后使用乐观或悲观锁写回原共享变量中,这样做的目的是为了使读操作无锁。

 在使用Copy-on-write时,有如下问题需要注意:

  • 仅适用于写操作少的情况
    若存在大量写操作的情况,会导致每一次写入都会创建一个变量的副本,造成大量的开销。
  • 允许短暂的读写不一致
    在存在写入时,读取的是原变量的未进行修改前的值,只能保证最终一致性,并不能保证强一致性。

 

2.3 线程本地存储 (Thread Local Storage, TLS)


 线程本地存储的原理是,不同线程使用不同的变量,避免共享。可以使用ThreadLocal来达到每个线程都拥有自己独有的资源,使用方式如下:

 

public class ThreadValue {

    /**
     * 定义每个线程独有的资源
     */
    final static AtomicLong THREAD_VALUE = new AtomicLong(0);


    /**
     * 定义并初始化ThreadLocal
     */
    final static ThreadLocal<Long> THREAD_LOCAL = ThreadLocal.withInitial(new Supplier<Long>() {

        @Override
        public Long get() {
            return THREAD_VALUE.getAndIncrement();
        }
    });


    /**
     * 获取独有资源的值
     * @return
     */
    public static Long get(){
        return THREAD_VALUE.get();
    }


    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1获得的变量:"+ ThreadValue.get());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2获得的变量:"+ ThreadValue.get());
            }
        }).start();
    }

}

 

 调用结果如下,由结果可知,不同线程使用的是不同的ThreadLocal对象

线程1获得的变量:0
线程2获得的变量:0

 注:在使用ThreadLocal需要注意内存泄漏问题,手动清理ThreadLocal对象。


ExecutorService es;
ThreadLocal tl;
es.execute(()->{
  //ThreadLocal增加变量
  tl.set(obj);
  try {
    // 省略业务逻辑代码
  }finally {
    //手动清理ThreadLocal 
    tl.remove();
  }
});

 

3.JUC中的锁

3.1 AbstractQueuedSynchronizer(AQS)

3.1.1 定义

 AbstractQueuedSynchronizer抽象队列同步器(AQS),提供一个框架来实现阻塞锁和依赖先进先出(FIFO)等待队列的相关同步器(信号量、事件等)。对于大多数依赖于单个原子int值来表示状态的同步器,这个类被设计成一个有用的基础

 

3.1.2 核心思想

 AQS核心思想是实现阻塞锁及同步器,需要具备如下的要点

 

3.1.2.1 记录同步状态(state变量)

 定义state变量,并提供了cas设置state的方法

  • state=0,,没有线程持有锁
  • state=1,有一个线程持有锁
  • state>1,该线程重入了锁

//共享变量,使用volatile修饰保证线程可见性
private volatile int state;


//返回同步状态的当前值
protected final int getState() {  
        return state;
}
 // 设置同步状态的值
protected final void setState(int newState) { 
        state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

 

3.1.2.2 记录当前持有锁的线程

 AQS父类AbstractOwnableSynchronizer 记录了持有锁的线程


public abstract class AbstractOwnableSynchronizer implements java.io.Serializable { 
	// 记录持有锁的线程
	private transient Thread exclusiveOwnerThread;  
}

 

3.1.2.3 支持阻塞/唤醒操作

 AQS使用LockSupport.park()使线程阻塞,LockSupport.unpark()精准唤醒对应线程

public class LockSupport {
	public static void park() { U.park(false, 0L); }
	public static void unpark(Thread thread) { if (thread != null) U.unpark(thread); }
}

 

 park/unpark与wait/notify的核心区别

park/unparkwait/notify
park与unpark无论先执行哪句都会唤醒对应线程先调用notify再调用wait会抛出IllegalMonitorStateException异常
park/unpark可以在线程的任意地方执行wait/notify只能在synchronized块中执行
park/unpark支持精准唤醒wait/notify只能唤醒随机的一个线程

 

3.1.2.4 阻塞队列(CLH)

 AQS内部实现CLH(Craig,Landin,and Hagersten)队列,实际的数据结构为双向链表,当node初始化时为空,head=tail=null,从尾结点入从头结点出

在这里插入图片描述

 

3.1.3 资源共享方式

 AQS定义如下两种资源共享方式

  • Exclusive(独占)
    只允许一个线程访问,如ReentrantLock

  • Share(共享)
    运行多个线程访问,如ReadWriteLock、Semaphore、CountDownLatch、 CyclicBarrier等

 

3.1.4 模板方法

 AQS采用模板方法定义了继承AQS需进行重写的方法即对state的获取和释放

 /**
     * 独占方式,尝试获取资源
     * @param arg
     * @return 获取资源是否成功
     */
    protected boolean tryAcquire(int arg)

    /**
     * 独占方式,尝试释放资源
     * @param arg
     * @return 释放资源是否成功
     */
    protected boolean tryRelease(int arg)

    /**
     * 共享方式,尝试获取资源
     * @param arg
     * @return 负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源
     */
    protected int tryAcquireShared(int arg)

    /**
     * 共享方式,尝试释放资源
     * @param arg
     * @return 释放资源是否成功
     */
    protected boolean tryReleaseShared(int arg)

    /**
     * 只有用到condition才需要去实现它。
     * @return 该线程是否正在独占资源
     */
    protected boolean isHeldExclusively()

 

3.1.5 核心方法

3.1.5.1 acquire

 acquire以独占模式获取资源,忽略中断,在线程阻塞过程中,中断无效,源码如下:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
	final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                //头结点出队
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //当获取(资源)失败后,检查并且更新结点状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //阻塞并检查是否中断
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

  • addWaiter
    为当前线程生成node插入队列尾部

  • acquireQueued
    头结点出队,执行资源申请,未申请成功的线程进入阻塞状态,不会响应中断 ,但会记录并返回中断状态

  • selfInterrupt
    中断补偿,由于中断时线程处于阻塞状态,故此时补偿响应中断

  • tryAcquire
    定义的模板方法由子类实现,计算当前线程获取锁后的state值

 

3.1.5.2 acquireShared

 acquireShared以共享模式获取资源,忽略中断,在线程阻塞过程中,中断无效,核心逻辑与acquire类似,增加唤醒后继节点的步骤setHeadAndPropagate,源码如下:

public final void acquireShared(int arg) {
     if (tryAcquireShared(arg) < 0)
         doAcquireShared(arg);
 }
 
private void doAcquireShared(int arg) {
	//为当前线程生成node插入队列尾部
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                //获取共享锁
                int r = tryAcquireShared(arg);
                //成功获取共享锁
                if (r >= 0) {
                	//用来设置新head,没有后继节点或者后继节点是共享类型,进行唤醒下调用doReleaseShared
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //当获取(资源)失败后,检查并且更新结点状态
            if (shouldParkAfterFailedAcquire(p, node) &&
               	//阻塞并检查是否中断
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • tryAcquireShared
    定义的模板方法由子类实现,计算当前线程获取锁后的state值

  • doAcquireShared
    头结点出队,执行资源申请,未申请成功的线程进入阻塞状态,不会响应中断 ,但会记录并返回中断状态

 

3.1.5.3 release

 提供独占模式下,资源释放的方法


    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

  • tryRelease
    父类定义的模板方法,计算当前线程释放锁后的state值(此时为单线程)

  • unparkSuccessor
    唤醒队列中的后继者

 

3.1.5.4 releaseShared

 提供共享模式下,资源释放的方法


public final boolean releaseShared(int arg) {
      if (tryReleaseShared(arg)) {
          doReleaseShared();
          return true;
      }
      return false;
  }

 private void doReleaseShared() {
     for (;;) {
         Node h = head;
         if (h != null && h != tail) {
             int ws = h.waitStatus;
             if (ws == Node.SIGNAL) {
                 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                     continue;            // loop to recheck cases
                 unparkSuccessor(h);
             }
             else if (ws == 0 &&
                      !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                 continue;                // loop on failed CAS
         }
         if (h == head)                   // loop if head changed
             break;
     }
 }

  • tryReleaseShared
    父类定义的模板方法,计算当前线程释放锁后的state值(此时为多线程,需要使用cas)

  • doReleaseShared
    唤醒后续的第一个获取写锁之前的所有获取读锁的节点,没有写锁将会唤醒整个队列(批量唤醒)

 

3.1.6 ConditionObject

3.1.6.1 源码结构

 ConditionObject是Condition的实现,子类可以使用该new ConditionObject()构造条件变量,使用的数据结构为双向链表,与锁使用的阻塞队列是同一个类

public class ConditionObject implements Condition, java.io.Serializable 
{ 
	private transient Node firstWaiter; 
	private transient Node lastWaiter; 
}

 

3.1.6.2 await

 await流程为,先释放锁,判断不在AQS阻塞队列中,不在阻塞,待signal唤醒,唤醒后在AQS阻塞队列中,重新获取锁


public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 加入Condition的等待队列
    Node node = addConditionWaiter();
    // 阻塞在Condition之前必须先释放锁,否则会死锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //不在AQS阻塞队列中
    while (!isOnSyncQueue(node)) {
        //阻塞当前对象
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 重新获取锁
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        // 被中断唤醒,抛中断异常
        reportInterruptAfterWait(interruptMode);
}

  • acquireQueued
    AQS队列,头结点出队,执行资源申请,未申请成功的线程进入阻塞状态,不会响应中断 ,但会记录并返回中断状态

  • isOnSyncQueue
    用于判断该Node是否在AQS的同步队列里面。初始的时候,Node只在Condition的队列里,而不在AQS的队列里。但执行signal操作的时候,会放进AQS的同步队列

  • checkInterruptWhileWaiting
    检测在park期间是否收到过中断信号。若收到中断信号,会使interruptMode!=0,退出循环并抛中断异常。未收到中断信号interruptMode=0 被唤醒后 检查是否在AQS的同步队列里 存在则退出循环

 

3.1.6.2 signal

 signal流程为头结点出Condition队列,放入AQS阻塞队列中,再唤醒该结点

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}


// 唤醒队列中的第1个线程
private void doSignal(Node first) {
   do {
       if ( (firstWaiter = first.nextWaiter) == null)
           lastWaiter = null;
       first.nextWaiter = null;
   } while (!transferForSignal(first) &&
            (first = firstWaiter) != null);
}

final boolean transferForSignal(Node node) {
   if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
       return false;
   // 先把Node放入互斥锁的同步队列中,再调用unpark方法
   Node p = enq(node);
   int ws = p.waitStatus;
   if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
       LockSupport.unpark(node.thread);
   return true;
}


 

3.2 Lock


 Lock是对外提供的接口,提供加锁解锁方法,包含的方法如下:
 

public interface Lock {

    /**
     * 加锁
     */
    void lock();

    /**
     * 可中断的加锁
     * @throws InterruptedException 可中断异常
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * 尝试获取锁,未获取到锁不进行阻塞
     * @return 是否成功获取锁
     */
    boolean tryLock();

    /**
     * 尝试获取锁,未获取到锁不进行阻塞,可设置超时时间
     * @param time 超时时间
     * @param unit 时间单位
     * @return 是否成功获取锁
     * @throws InterruptedException 可中断异常
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     * 解锁
     */
    void unlock();

    /**
     * 初始化条件变量
     * @return 条件变量
     */
    Condition newCondition();
}

 

3.3 Condition


 Condition是对外提供的条件相关的接口,提供阻塞唤醒方法(类似wait/notify),包含的方法如下:
 

public interface Condition {

    /**
     * 阻塞,可中断
     * @throws InterruptedException 中断异常
     */
    void await() throws InterruptedException;

    /**
     * 阻塞,不可中断
     * @throws InterruptedException 中断异常
     */
    void awaitUninterruptibly();


    /**
     * 等待多少纳秒后唤醒,可中断
     * @param nanosTimeout 等待的纳秒的long型数值
     * @return 剩余等待时间 <=0 代表超时
     * @throws InterruptedException 中断异常
     */
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    /**
     * 等待多少时间后唤醒,可中断
     * @param time 等待时间
     * @param unit 时间单位
     * @return false 等待超时 true 未到时间
     * @throws InterruptedException 中断异常
     */
    boolean await(long time, TimeUnit unit) throws InterruptedException;


    /**
     * 等待到截止日期后唤醒,可中断
     * @param deadline 截止日期
     * @return false 等待超时 true 未到时间
     * @throws InterruptedException 中断异常
     */
    boolean awaitUntil(Date deadline) throws InterruptedException;


    /**
     * 唤醒第一个
     */
    void signal();

    /**
     * 唤醒全部
     */
    void signalAll();
}

 

3.4 ReentrantLock

3.4.1 继承类图

 如下图所示

在这里插入图片描述
 

3.4.2 源码结构

 ReentrantLock意为可重入锁(同一线程可以多次获取同一把对象锁),内部存在Sync抽象类,NonfairSync(非公平锁实现)及FairSync(公平锁实现),实现了lock接口,lock接口中的方法实现由Sync及其子类来实现。

 构造方法默认使用非公平锁,也支持传入是否公平参数来对应构造公平或非公平锁
 

public class ReentrantLock implements Lock, java.io.Serializable {

	private final Sync sync;

	abstract static class Sync extends AbstractQueuedSynchronizer{}

	static final class NonfairSync extends Sync{}

	static final class FairSync extends Sync{}

    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    public void lock() {
        sync.lock();
    }

    public void unlock() {
        sync.release(1);
    }

}
3.4.3 NonfairSync

 非公平锁的实现,即直接尝试设置state的值不管是否存在排队的线程


final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //state为0 说明没有线程占用锁
    if (c == 0) {
        //直接尝试设置state的值,不管是否存在排队的线程
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果state不为为0 且占有线程的是当前线程 state+1 表示重入
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

 

3.4.4 FairSync

 公平锁的实现,需要调用hasQueuedPredecessors()判断是否有等待的线程,并尝试设置state为1

 protected final boolean tryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
     //state为0 说明没有线程占用锁
     if (c == 0) {
     	//判断是否有等待的线程,并尝试设置state为1
         if (!hasQueuedPredecessors() &&
             compareAndSetState(0, acquires)) {
             setExclusiveOwnerThread(current);
             return true;
         }
     }
     //如果state不为为0 且占有线程的是当前线程 state+1 表示重入
     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.4.5 使用示例

 生产者消费者模式,实现如下

public class MyQueue<E> {


    /**
     * 锁
     */
    private final Lock lock;

    /**
     * 消费者条件队列
     */
    private final Condition notEmpty;


    /**
     * 生产者条件队列
     */
    private final Condition notFull;

    /**
     * 阻塞队列
     */
    private final Object[] items;


    /**
     * 消费索引
     */
    private int takeIndex;

    /**
     * 生产索引
     */
    private int putIndex;


    /**
     * 队列数量
     */
    private int count;


    public MyQueue(int capacity) {
        items = new Object[capacity];
        lock = new ReentrantLock();
        notEmpty = lock.newCondition();
        notFull = lock.newCondition();
    }

    public void enqueue(E x) {
        lock.lock();
        try {
            //队列满时阻塞生产者
            while (count == items.length) {
                try {
                    notFull.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //入队
            items[putIndex] = x;

            //到边界生产索引置为0
            if (++putIndex == items.length) {
                putIndex = 0;
            }
            count++;

            //不空时唤醒消费者
            notEmpty.signal();
        } finally {
            lock.unlock();
        }

    }

    public E dequeue() {
        lock.lock();
        try {
            //队列空时阻塞消费者
            while (count == 0) {
                try {
                    notEmpty.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //出队并清空
            E item = (E) items[takeIndex];
            items[takeIndex] = null;

            //到边界消费索引置为0
            if (++takeIndex == items.length) {
                takeIndex = 0;
            }
            count--;
            //不满时唤醒生产者
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }

}






public class QueueTest {


    public static void main(String[] args) {

        MyQueue<String> queue = new MyQueue<>(10);

        CustomerThread customerThread;
        for (int i = 0; i < 10; i++) {
            customerThread = new CustomerThread(queue);
            customerThread.start();
        }

        ProducerThread producerThread;
        for (int i = 0; i < 10; i++) {
            producerThread = new ProducerThread(queue);
            producerThread.start();
        }



    }


    private static class ProducerThread extends Thread {

        private final MyQueue<String> queue;


        private final Random random = new Random();

        public ProducerThread(MyQueue<String> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            String name = "物品" + random.nextInt(100);
            System.out.println("线程"+Thread.currentThread().getName()+"开始生产物品"+name);
            queue.enqueue(name);
        }
    }


    private static class CustomerThread extends Thread {

        private final MyQueue<String> queue;


        public CustomerThread(MyQueue<String> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            String name = queue.dequeue();
            System.out.println("线程"+Thread.currentThread().getName()+"开始消费物品"+name);
        }
    }

}

 

3.5 ReentrantReadWriteLock

3.5.1 继承类图

 如下图所示

在这里插入图片描述

 

3.5.2 源码结构

 ReentrantReadWriteLock意为可重入读写锁(读读不互斥、写读不互斥、读写互斥、写写互斥),实现了ReadWriteLock接口,内部存在Sync抽象类,由NonfairSync(非公平锁实现)及FairSync(公平锁实现),Lock接口由ReadLock(读锁)及WriteLock(写锁)实现

 构造方法默认使用非公平锁,也支持传入是否公平参数来对应构造公平或非公平锁
 

public interface ReadWriteLock {
  
    Lock readLock();

    Lock writeLock();
}


public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {

 private final ReentrantReadWriteLock.ReadLock readerLock;
   
 private final ReentrantReadWriteLock.WriteLock writerLock;

 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; }

abstract static class Sync extends AbstractQueuedSynchronizer{}

static final class NonfairSync extends Sync{}

static final class FairSync extends Sync{}

public static class ReadLock implements Lock, java.io.Serializable{}

public static class WriteLock implements Lock, java.io.Serializable{}

}

 

3.5.3 Sync

3.5.3.1 state

 ReentrantReadWrite用一个state变量同时表示读锁和写锁,高16读锁,低16位为写锁,读锁写锁都可重入,最大次数为65535,这样就可以实现用一个CAS操作设置读锁和写锁的值。当state!=0时,要么持有读锁,要么持有写锁,通过sharedCount及exclusiveCount方法来判断持有的是哪个锁。

 

abstract static class Sync extends AbstractQueuedSynchronizer {
    // 版本序列号
    private static final long serialVersionUID = 6317671515068378041L;        
    // 高16位为读锁,低16位为写锁
    static final int SHARED_SHIFT   = 16;
    // 读锁单位
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    // 读锁最大数量
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    // 写锁最大数量
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    // 本地线程计数器
    private transient ThreadLocalHoldCounter readHolds;
    // 缓存的计数器
    private transient HoldCounter cachedHoldCounter;
    // 第一个读线程
    private transient Thread firstReader = null;
    // 第一个读线程的计数
    private transient int firstReaderHoldCount;
	//获得读锁数量
	static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
	//获得写锁数量
	static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}


 

3.5.3.2 HoldCounter

 HoldCounter用于保存持有持有共享锁的线程个数及线程id

// 计数器
static final class HoldCounter {
    // 计数
    int count = 0;
    // 获取当前线程的TID属性的值
    final long tid = getThreadId(Thread.currentThread());
}

 

3.5.3.3 ThreadLocalHoldCounter

 通过ThreadLocal保存每个进入的线程重入锁的个数,以及对应ThreadId的值,记录每个线程的重入次数

// 本地线程计数器
static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    // 重写初始化方法,初始化HoldCounter
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

 

3.5.3.4 tryAcquire(重写AQS)

 独占式线程获取锁的方法,返回值true表示获取资源成功,false为失败

protected final boolean tryAcquire(int acquires) {
  
    Thread current = Thread.currentThread();
    //获取状态
    int c = getState();
    //获取写线程数量
    int w = exclusiveCount(c);
    //状态非0 要么是读 要么是写
    if (c != 0) {
        // 如果没有写 那么必定是读 有读时不能写 返回获取锁失败
        // 如果有写 非重入(当前线程不为所有者)返回获取锁失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
   		//超过最大写线程数量
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 线程重入
        setState(c + acquires);
        return true;
    }
    //状态为0 说明没有线程在写或读 可以获取锁
    //如果写应该阻塞或者cas设置状态未成功 返回获取锁失败
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    //获取锁成功
    setExclusiveOwnerThread(current);
    return true;
 }

 

3.5.3.5 tryAcquireShared(重写AQS)

 共享式线程获取锁的方法,返回值小于0失败,大于0成功

 protected final int tryAcquireShared(int unused) {
   
     Thread current = Thread.currentThread();
     //获取状态
     int c = getState();
     //有写锁或当前线程不为锁持有者 获取锁失败
     if (exclusiveCount(c) != 0 &&
         getExclusiveOwnerThread() != current)
         return -1;
     //获取读线程数量    
     int r = sharedCount(c);
     //读不阻塞 并且读线程数量小于最大数量  并且cas设置状态成功
     if (!readerShouldBlock() &&
         r < MAX_COUNT &&
         compareAndSetState(c, c + SHARED_UNIT)) {
         //为当前第一个读线程
         if (r == 0) {
             firstReader = current;
             firstReaderHoldCount = 1;
         } else if (firstReader == current) {
         	 //读线程重入
             firstReaderHoldCount++;
         } else {
         	 //读锁数量不为0并且不为当前线程 更新缓存
         	 //缓存的线程ID及数量
             HoldCounter rh = cachedHoldCounter;
             //未缓存或缓存的不是当前线程 设置为当前线程 readHolds为ThreadLocal
             if (rh == null || rh.tid != getThreadId(current))
                 cachedHoldCounter = rh = readHolds.get();
             //有当前线程的缓存且HoldCounter未初始化
             else if (rh.count == 0)
                 readHolds.set(rh);
             //设置缓存及readHolds对象中的HoldCounter数量+1
             rh.count++;
         }
         return 1;
     }
     //读阻塞或读线程大于最大数量或者cas设置失败 死循环用来保证相关操作可以成功
     return fullTryAcquireShared(current);
 }

 

3.5.3.6 tryRelease(重写AQS)

 独占式线程释放锁的方法

protected final boolean tryRelease(int releases) {
	 //不为独占线程 抛异常
     if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
     int nextc = getState() - releases;
     boolean free = exclusiveCount(nextc) == 0;
     if (free)
         setExclusiveOwnerThread(null);
     setState(nextc);
     return free;
 }

 

3.5.3.7 tryReleaseShared(重写AQS)

 共享式线程释放锁的方法,更改state的值,清除缓存中ThreadLocal HoldCounter的值等操作。

 protected final boolean tryReleaseShared(int unused) {
     Thread current = Thread.currentThread();
     if (firstReader == current) {
         // assert firstReaderHoldCount > 0;
         if (firstReaderHoldCount == 1)
             firstReader = null;
         else
             firstReaderHoldCount--;
     } else {
         HoldCounter rh = cachedHoldCounter;
         if (rh == null || rh.tid != getThreadId(current))
             rh = readHolds.get();
         int count = rh.count;
         if (count <= 1) {
             readHolds.remove();
             if (count <= 0)
                 throw unmatchedUnlockException();
         }
         --rh.count;
     }
     for (;;) {
         int c = getState();
         int nextc = c - SHARED_UNIT;
         if (compareAndSetState(c, nextc))
             // Releasing the read lock has no effect on readers,
             // but it may allow waiting writers to proceed if
             // both read and write locks are now free.
             return nextc == 0;
     }
 }

 

3.5.4 NonfairSync、FairSync

 可以看到NonfairSync(非公平实现)、FairSync(公平实现)内部只实现了两个方法,核心逻辑在Sync中

static final class NonfairSync extends Sync {
	// 写线程抢锁的时候是否应该阻塞
	final boolean writerShouldBlock() {
	// 写线程在抢锁之前永远不被阻塞,非公平锁
	return false;
	}
	// 读线程抢锁的时候是否应该阻塞
	final boolean readerShouldBlock() {
	// 读线程抢锁的时候,当队列中第一个元素是写线程的时候要阻塞
	return apparentlyFirstQueuedIsExclusive();
	}
}
static final class FairSync extends Sync {
	// 写线程抢锁的时候是否应该阻塞
	final boolean writerShouldBlock() {
	// 写线程在抢锁之前,如果队列中有其他线程在排队,则阻塞。公平锁
	return hasQueuedPredecessors();
	}
	// 读线程抢锁的时候是否应该阻塞
	final boolean readerShouldBlock() {
	// 读线程在抢锁之前,如果队列中有其他线程在排队,阻塞。公平锁
	return hasQueuedPredecessors();
	}
}


 

3.5.5 ReadLock、WriteLock

 ReadLock(读锁)、WriteLock(写锁)的核心逻辑在Sync中实现。写锁支持Condition,读锁不支持Condition

public static class ReadLock implements Lock, java.io.Serializable {
	private final Sync sync;
	protected ReadLock(ReentrantReadWriteLock lock) {
       sync = lock.sync;
    }
	public void lock() {sync.acquireShared(1);}
	public void unlock() {sync.releaseShared(1);}
	public Condition newCondition() {throw new UnsupportedOperationException();}
	//....lock接口中的其他方法
}

public static class WriteLock implements Lock, java.io.Serializable {
	private final Sync sync;
	protected WriteLock(ReentrantReadWriteLock lock) {
       sync = lock.sync;
    }
	public void lock() {sync.acquire(1);}
	public void unlock() {sync.release(1);}
	public Condition newCondition() {return sync.newCondition();}
	//....lock接口中的其他方法
}

 

3.5.6 锁降级与升级

3.4.3.1 锁升级

 先持有读锁,不释放读锁的情况下,持有写锁。释放读锁后,再获取写锁不属于锁升级


//读锁
r.lock();
try {
	//写锁
    w.lock();
    try {
      //执行业务逻辑
    } finally{
      w.unlock();
    }
} finally{
  r.unlock();  
}

注:ReentrantReadWriteLock不支持锁升级,因为读锁存在时,无法获取写锁(读写互斥),会导致写锁永久阻塞,读锁也不能正确释放

 

3.5.3.1 锁降级

 现持有写锁,不释放写锁的情况下,持有读锁。释放写锁后,再获取读锁不属于锁降级

 

//写锁
w.lock();
try {
  //执行业务逻辑
  //读锁
  r.lock();
} finally{
  w.unlock();
}
try {
	//此时获取的是最新的数据
} finally{
  r.unlock();  
}


 注:ReentrantReadWriteLock支持锁降级,因为写读不互斥,且读锁可以直接获取到。这样做的目的是为了保证数据可见性,例如释放写锁后,另一个线程进行写入修改,当前线程获取读锁,读取到的就不是最新的数据(写读不互斥),但若在未释放写锁时,获取读锁,就会阻塞另一个线程的写入(读写互斥)

 

3.5.7 使用示例

 实现缓存,需要包含put方法,及按需加载的get方法

public class Cache<K, V> {

   private final Map<K, V> cache = new HashMap<>();

   private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

   private final Lock readLock = readWriteLock.readLock();

   private final Lock writeLock = readWriteLock.writeLock();

   public V get(K key) {
       V value = null;
       readLock.lock();
       try {
           value = cache.get(key);
       } finally {
           readLock.unlock();
       }
       if (value != null) {
           return value;
       }
       //缓存不存在,写入缓存
       writeLock.lock();
       try {
           value = cache.get(key);
           //防止其他线程已查询过数据库
           if(value==null){
               //模拟查询数据库操作
               value = (V)new Object();
               cache.put(key,value);
           }
       }finally {
           writeLock.unlock();
       }
       
       return value;

   }



   public void put(K key, V value) {
       writeLock.lock();
       try {
           cache.put(key, value);
       }finally {
           writeLock.unlock();
       }
   }

}

 

3.6 StampedLock

3.6.1 定义

 在JDK8中,新增了带版本戳的读写锁StampedLock,使用时需传入版本戳(同数据库乐观锁Version使用类似),并不是基于现有的AQS实现,而是重新实现了一个阻塞队列,内部实现了乐观读、悲观读、悲观写三种策略,乐观读的引入,便是为了在读写锁的基础上,提高并发度使读写不互斥,减少写线程被饿死的风险,同时由于乐观读使用CAS无锁方案,效率高于读写锁。

 

3.6.2 使用示例

3.6.2.1 悲观读、悲观写示例

final StampedLock sl = new StampedLock();
  
// 获取/释放悲观读锁示意代码
long stamp = sl.readLock();
try {
  //省略业务相关代码
} finally {
  sl.unlockRead(stamp);
}

// 获取/释放写锁示意代码
long stamp = sl.writeLock();
try {
  //省略业务相关代码
} finally {
  sl.unlockWrite(stamp);
}

 

3.6.2.2 乐观读示例

 来自官网示例,实现逻辑为尝试乐观读tryOptimisticRead,判断在读的过程中是否被修改过validate,被修改过则升级为悲观读



class Point {
  private int x, y;
  final StampedLock sl = new StampedLock();
  //计算到原点的距离  
  int distanceFromOrigin() {
    // 乐观读
    long stamp = sl.tryOptimisticRead();
    // 保存局部变量,读的过程数据可能被修改
    int curX = x, curY = y;
    //判断执行读操作期间,是否存在写操作,如果存在,则sl.validate返回false
    if (!sl.validate(stamp)){
      // 升级为悲观读锁
      stamp = sl.readLock();
      try {
        curX = x;
        curY = y;
      } finally {
        //释放悲观读锁
        sl.unlockRead(stamp);
      }
    }
    return Math.sqrt(curX * curX + curY * curY);
  }
}

 

3.6.3 注意事项

 StampedLock在使用过程中需要注意如下几点:

  • StampedLock写锁不支持重入
    StampedLock用低8位表示读和写的状态,其中低7位表示读锁,第8位表示写锁的状态,因为写锁只有一个bit位,所以写锁是不可重入的。
  • StampedLock 不支持条件变量
    StampedLock 的悲观读锁、写锁都不支持条件变量
  • StampedLock readLock或writeLock不能调用中断操作
    如果线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。如果需要中断需要使用悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值