超时加锁、countDownLatch、读写锁

269 阅读7分钟

重量锁概述

重量锁:发生系统调用-(操作系统做内核态及用户态之间的切换,会带来一定的性能损耗)

java线程模型

java的线程 Thread 本质上就是jvm 堆中的一个对象。Thread被执行就是cpu调度,线程是操作系统最小执行单位。

java线程与OS(操作系统)线程的关系

linux操作系统中 glibc 库提供了 pthread_create函数用于创建一个新的线程,linux中可以通过man命令查看 *thread 线程指针 用于接收线程的 pid,pid中存的是线程的内存地址 *attr 为线程属性
void *(*start_routine) (void *)线程启动后的主体函数,相当于java的线程run方法 void *arg 主体函数的参数

 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)

java中如何创建启动一个线程(这里的线程指的是cpu调度而非Thread对象)? 下面是 Thread 类的 start 方法

public synchronized void start() {
   if (threadStatus != 0)
       throw new IllegalThreadStateException();

   /* Notify the group that this thread is about to be started
    * so that it can be added to the group's list of threads
    * and the group's unstarted count can be decremented. */
   group.add(this);

   boolean started = false;
   try {
       start0(); //实际线程启动
       started = true;
   } finally {
       try {
           if (!started) {
               group.threadStartFailed(this);
           }
       } catch (Throwable ignore) {
           /* do nothing. If start0 threw a Throwable then
             it will be passed up the call stack */
       }
   }
}

实际上调用了本地方法 start0, 这是一个本地方法;调用了C++的 JVMThreadStart函数;C++函数创建一个Thread对象又最终通过OS::create_thread(this,thr_type_stack_sz) 操作系统调用函数创建线程。对应的linux调用 pthread_create,windows调用 HANDLE WINAPI CreateThread

private native void start0();

结论:java的线程和本地线程是一一对应的。

sychronized关键字如何保证互斥

在java中线程的本质就是一个本地线程,因此java中线程互斥即OS(操作系统)层面的互斥。
OS级别通过 互斥量:mutex, mutex是OS中的一个变量,当被线程持有时其他线程不得持有。 pthread_mutex_t *mutex 加锁线程指针
pthread mutex_lock(&&mutex) 加锁 线程取地址
pthread mutex_unlock(&mutex) 解锁 线程取地址

超时加锁 tryLock

Lock lock = new ReentrantLock();
lock.lock();
new Thread(()->{
    log.debug("开始尝试加锁");
    if(!lock.tryLock()){ //只尝试一次加锁
        log.debug("加锁失败");
    }
},"t1").start();
new Thread(()->{
    log.debug("开始尝试加锁");
    try {
        //指定时间内反复尝试加锁
        if(!lock.tryLock(3,TimeUnit.SECONDS)){ 
            log.debug("加锁失败");
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
},"t2").start();

tryLock

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
        //unit.toNanos 转纳秒 
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

tryAcquireNanos

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

doAcquireNanos

private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        //死循环
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            //时间计算 打断循环
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

线程打断 interrupt

Thread t1 = new Thread(()->{
    log.debug("------------t1--------------------");
    //第一次返回 true  第二次返回false
    log.debug("在t1线程中第一次调用 Thread.interrupted()=[{}]",Thread.interrupted());
    log.debug("在t1线程中第二次调用 Thread.interrupted()=[{}]",Thread.interrupted());
    for (int i = 0; i<20;i++ ){
        log.debug("i=[{}]",i);
    }
},"t1");
t1.start();
//实际上不会打断t1线程执行
t1.interrupt();
// 主线程中调用 返回的是主线程的interrupted 而不是当前对象的线程 
//interrupted 是个静态方法 不需要实例对象调用 
//这里的t1.interrupted 实际上就是Thread.interrupted
log.debug("在主线程中调用 t1.interrupted()=[{}]",t1.interrupted());
log.debug("在主线程中调用 t1.interrupted()=[{}]",t1.interrupted());
// isInterrupted 一直为true 不回去清除打断标记
log.debug("在主线程中调用 t1.isInterrupted()=[{}]",t1.isInterrupted());

运行结果

image.png

结论

interrupted 不会打断正在执行的线程,这里的打断实际上是打断被阻塞的线程 interrupted 会返回当前线程的打断标记; interrupted 会清除打断标记 isInterrupted 不会清除线程打断标记

CountDownLatch

CountDownLatch countDownLatch = new CountDownLatch(3);//计数器 计数 3
Thread t1 = new Thread(()->{
    log.debug("t1");
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    countDownLatch.countDown();
},"t1");
Thread t2 = new Thread(()->{
    log.debug("t2");
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    countDownLatch.countDown();
},"t2");
Thread t3 = new Thread(()->{
    log.debug("t3");
    countDownLatch.countDown();
},"t3");

t1.start();
t2.start();
t3.start();

//阻塞 直到 计数为0才继续执行
//本质上是通过 getState() 判断的 AQS文章中有讲
countDownLatch.await();

log.debug("latched ===========");

countDownLatch.countDown()源码

public void countDown() {
    sync.releaseShared(1);
}

尝试解阻塞

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

tryReleaseShared

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        //先判断再减 1 因此这里的 == 0 判断的是初始化状态
        if (c == 0)
            return false;
        //countdown 调用一次 state - 1 
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

doReleaseShared 拿到头部节点,唤醒头部线程对象

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

countDownLatch.await()源码

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

当state 为0 时这个方法返回值为 -1

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

acquireSharedInterruptibly 当tryAcquireShared 返回-1时调用doAcquireSharedInterruptibly

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
        
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

join和latch的区别

join是等待线程死亡后才继续执行,latch不需要线程死亡 只要计数为0就会继续执行。 一般开发当中 join不适应线程池 线程池通常是线程复用的

读锁与写锁

  • 读读并发
ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock r =  rw.readLock();
        ReentrantReadWriteLock.WriteLock w =  rw.writeLock();
//        ---- t1和t2 两个都时读锁时 并发执行 ----
        Thread t1 = new Thread(()->{
            r.lock();
            log.debug("t1拿到了锁");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r.unlock();

        },"t1");

        t1.start();

        Thread t2 = new Thread(()->{
            r.lock();
            log.debug("t2拿到了锁");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r.unlock();

        },"t2");

        t2.start();
    }

运行结果

image.png

  • 读写互斥
ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock r =  rw.readLock();
        ReentrantReadWriteLock.WriteLock w =  rw.writeLock();
//        ---- t1和t2 t1是写锁 t2是读锁 互斥执行 ----
        Thread t1 = new Thread(()->{
            w.lock();
            log.debug("t1拿到了锁");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            w.unlock();

        },"t1");

        t1.start();

        Thread t2 = new Thread(()->{
            r.lock();
            log.debug("t2拿到了锁");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r.unlock();

        },"t2");

        t2.start();
    }

运行结果

image.png

结论: 当持锁线程唤醒的线程是读锁,后续连续的读锁线程可并发执行,如果队列中有写锁线程,那么写锁之后的读锁线程是不可并发执行的。

读写锁经典案例—— 源码中有

class CacheData {
    //缓存数据
    Object data;
    //缓存数据是否过期
    volatile boolean cacheValid;
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    //处理缓存数据
    void processCachedData() {
      //读取数据
      rwl.readLock().lock();
      //如果数据过期
      if (!cacheValid) {
        //释放读锁;获取写锁
        rwl.readLock().unlock();
        rwl.writeLock().lock();//这里拿写锁时有可能其他线程已拿到写锁
        try {
          // 双重保证 再次判断以防其他线程获取了写锁改变数据
          if (!cacheValid) {
            data = query db
            cacheValid = true;
          }
          // 释放写锁之前获取读锁来降级
          rwl.readLock().lock();
        } finally {
          // 释放写锁后 可以继续持有读锁
          rwl.writeLock().unlock();
        }
      }
      //获取数据使用权
      try {
        use(data);
      } finally {
          //最终释放读锁
        rwl.readLock().unlock();
      }
    }
  }

读写锁支持w-r降级重入(不支持r-w升级重入),单个线程读写锁没有互斥。

读写锁支持 w-r的重入

ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock r = rw.readLock();
ReentrantReadWriteLock.WriteLock w = rw.writeLock();
Thread t1 = new Thread(() -> {
    w.lock();
    log.debug("t1拿到了w锁");
    r.lock();
    log.debug("t1拿到了r锁");
    r.unlock();
    w.unlock();
}, "t1");

t1.start();

image.png 不支持读锁到写锁

ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock r = rw.readLock();
ReentrantReadWriteLock.WriteLock w = rw.writeLock();
Thread t1 = new Thread(() -> {
    r.lock();
    log.debug("t1拿到了w锁");
    w.lock();
    log.debug("t1拿到了r锁");
    w.unlock();
    r.unlock();
}, "t1");

t1.start();

拿不到锁,因为如果支持r-w的重入 并发场景下会破坏读写互斥。并发场景下假设t1 拿到读锁准备获取写锁,此时t2也可以拿到读锁,这会导致t1获取不到写锁。 image.png

读写锁源码

t1 写锁释放锁

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    //释放锁
    if (tryRelease(arg)) {
        Node h = head;//拿到头节点t1
        //头节点t1是否存在下一个节点
        if (h != null && h.waitStatus != 0) //waitStatus 为-1 表示该节点有下一个节点
            unparkSuccessor(h); //释放头节点t1
        return true;
    }
    return false;
}

int state 锁状态 高16位表示读锁,低16位表示写锁

protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
        //状态减 1
    int nextc = getState() - releases;
    //判断锁状态是否为0  是否重入
    boolean free = exclusiveCount(nextc) == 0;
    //如果是则进行解锁  
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;//返回
}

unparkSuccessor(h)

private void unparkSuccessor(Node node) {
    //如果拿到的头节点(t1)的状态小于0 则将状态改为0 出队
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    //获取t1的下个节点t2
    Node s = node.next;
    //t2是空 或者t2是队尾
    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;
    }
    //t2不是空 对s的线程进行解锁
    if (s != null)
        LockSupport.unpark(s.thread);//唤醒t2 
}

这里唤醒t2 是读锁加的锁,需要看读锁的加锁

public void lock() {
    //t2获取读锁
    sync.acquireShared(1);
}
public final void acquireShared(int arg) {
    // 由于当前持锁线程t1是写锁,返回-1,t2读锁加锁失败
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

循环尝试加锁

private void doAcquireShared(int arg) {
    //t2加锁失败 入队
    final Node node = addWaiter(Node.SHARED);//这里的Node.SHARED是个空的Node对象 线程名称也没有
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //死循环获取上个节点
            final Node p = node.predecessor();
            //判断上个节点是不是head
            if (p == head) {
                //尝试加锁
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    //加锁成功
                    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);
    }
}

这里的mode是 当前线程的 nextWaiter。读锁加锁失败时传的是 SHARED 共享锁(值为空Node节点)。写锁加锁失败时这里传的是 EXCLUSIVE 排他锁 (值为NULL)。 nextWaiter属性仅仅是为了一个标识。设计成一个Node对象主要是为了通用,以便于其他对AQS框架实现的使用。

Node(Thread thread, Node mode) {     
    this.nextWaiter = mode;
    this.thread = thread;
}

t2入队

private Node addWaiter(Node mode) {
    //新建一个 当前线程的node对象
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

加锁成功将自己设为队头

private void setHeadAndPropagate(Node node, int propagate) {
    //记录上次head节点 t2
    Node h = head;
    //把当前加锁成功节点设置为head
    setHead(node);
    //propagate > 0表示加锁成功
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;//拿到当前节点下一个 t3 
        if (s == null || s.isShared()) //isShared 判断 t3是不是读锁 
            doReleaseShared();//如果t3也是读锁释放t3
    }
}

判断nextWaiter 是不是SHARED(空Node对象),如果是说明当前线程加的是读锁

final boolean isShared() {
    return nextWaiter == SHARED;
}

接着释放读锁

private void doReleaseShared() {
    for (;;) { //死循环 再拿头节点t3
        Node h = head;
        if (h != null && h != tail) { //不为空且不是队尾
            int ws = h.waitStatus; 
            if (ws == Node.SIGNAL) { // ws == -1 即还有下个节点
                //t2状态修改失败则跳过本次循环
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            
                unparkSuccessor(h);//回到上面源码,t3修改成功 唤醒下一个
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

读写锁加锁过程

写锁上锁过程

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    //获取锁的状态
    int c = getState();
    //是否上了写锁  这个函数里面对SHARED 做了左移 取高16位 即写锁
    int w = exclusiveCount(c);
    //状态不等于0标识 锁被持有
    if (c != 0) {
        //w==0 当前加的读锁(不能升级返回false加锁失败)
        // w!=0 加的写锁,写锁加的不是自己 写锁互斥 返回false加锁失败
        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;
    }
    //判断是否需要阻塞
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
     //设置当前线程为持锁线程   
    setExclusiveOwnerThread(current);
    return true;
}

读锁上锁过程

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    // exclusiveCount(c) != 0 如果上了写锁,并且上锁的对象不是当前线程
    // 这里判断自身主要是考虑到自身加写锁的情况是可以重入降级为读锁的
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
        //获取读锁重入次数
    int r = sharedCount(c);
    //判断读锁是否需要阻塞  设置读锁重入次数
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
        //如果是第一个线程第一次加锁(前面没有人加锁),将当前线程赋值给firstReader
            firstReader = current;
            //记录当前线程加锁次数
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;//加锁成功
    }
    return fullTryAcquireShared(current);
}