当前位置: 首页 > news >正文

J.U.C(1)

目录

  • JUC(一)
    • 一:AQS
    • 二:reentrantlock原理
      • 1:加锁:
      • 2:解锁
      • 3:可重入锁原理
      • 4:可打断原理
      • 5:公平锁原理
      • 6:条件变量
    • 三:读写锁(reentrantreadwriteLock)
        • 应用之缓存
        • 补充
        • 原理-加写锁
        • 原理-加读锁
        • 原理-释放写锁
        • 原理-释放读锁

JUC(一)

一:AQS

  • aqs全称是AbstractQueuedSynchronizer,是一种阻塞式锁和同步式工具的框架;

state:

  • state表示资源的状态(包含独占模式和共享模式)。子类需要定义如何维护这些状态,包含获取锁和释放锁;
  • getstate是获取state;
  • setstate是设置state;
  • compareAndsetState是使用cas机制设置state;
  • 独占模式是指只能有一个线程去访问资源,共享模式是可以同时有多个线程去访问资源;
  • 提供了基于fifo,先进先出的等待队列,类似于monitor的entryList;
  • 条件变量用来实现等待和唤醒机制,支持多个条件变量,类似于monitor的waitset

需要子类实现的方法:

子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

获取锁:

// 如果获取锁失败
if (!tryAcquire(arg)) {// 入队, 可以选择阻塞当前线程 park unpark
}

释放锁:

// 如果释放锁成功
if (tryRelease(arg)) {// 让阻塞线程恢复运行
}
  • 自己实现一个不可重入锁,通过同步器;
class MyLock implements Lock{class Mysych extends AbstractQueuedSynchronizer {@Overrideprotected boolean tryAcquire(int arg) {if (compareAndSetState(0,1)){setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}protected Condition newCondition() {return new ConditionObject();}@Overrideprotected boolean tryRelease(int arg) {setExclusiveOwnerThread(null);setState(0);return true;}@Overrideprotected boolean isHeldExclusively() {return getState()==1;}}Mysych mysych=new Mysych();@Override//加锁,但是加锁失败就将线程加入等待队列public void lock() {mysych.acquire(1);}@Override//可中断public void lockInterruptibly() throws InterruptedException {mysych.acquireInterruptibly(1);}@Override//尝试加锁,和lock不同,只会加一次锁,失败了就会返回falsepublic boolean tryLock() {return mysych.tryAcquire(1);}@Override//带超时时间加锁public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return mysych.tryAcquireNanos(1,unit.toNanos(time));}@Override//释放锁public void unlock() {mysych.tryRelease(0);}@Override//条件变量public Condition newCondition() {return mysych.newCondition();}
}

自己实现的锁需要实现lock接口,然后内部的一个同步器需要继承aqs类,并实现其中的一些方法,然后使用同步器的方法去实现lock接口的方法;

二:reentrantlock原理

1:加锁:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • reentrantlock实现了lock接口,然后还实现了一个内部的同步器,同步器是继承了aqs的,同步器的分为公平锁的同步器和非公平锁的同步器;

然后一般创建reentrantlock默认是创建的非公平锁:

public ReentrantLock() {sync = new NonfairSync();
}
  • 没有竞争时,直接将state设为1,然后将owner设为当前线程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 当出现竞争时:

就会进行aquire方法:

public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}
  • 首先还是会再次尝试获取锁,如果获取锁失败就会执行acquireQueued,这里就会将当前线程加入等待队列,等待队列是一个双向链表。
  • Node是懒惰创建的;
  • Node有waitstatus,默认是0表示正常的;
  • 刚开始第一个Node被称为哨兵, 只是用来占位的,不予其他线程相关联;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

final boolean acquireQueued(final Node node, int arg) {boolean interrupted = false;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCreturn interrupted;}if (shouldParkAfterFailedAcquire(p, node))interrupted |= parkAndCheckInterrupt();}} catch (Throwable t) {cancelAcquire(node);if (interrupted)selfInterrupt();throw t;}}
  • 进入到acquireQueued逻辑,多次尝试获取锁,如果获取锁失败就会进入park阻塞;
  • 循环中,获取当前节点的前驱节点,如果前驱节点是头节点,然后再获取锁,如果还是失败就会进入shouldParkAfterFailedAcquire
  • shouldParkAfterFailedAcquire逻辑中,会将前驱节点的waitstatus设置为-1,然后这次会返回false;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 进入下一次循环,再次尝试获取锁,还是失败;
  • 然后进入shouldParkAfterFailedAcquire,这次返回true,进入if里面的逻辑,将线程阻塞;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

之后如果多次竞争失败就会变成这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2:解锁

public void unlock() {sync.release(1);
}
  • 解锁时调用unlock方法;
public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
}
  • release将state状态设为0,设置成功之后,他就会判断头节点的waitstatus是不是为-1,如果为-1就去唤醒头节点之后的节点;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling.  It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)node.compareAndSetWaitStatus(ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node.  But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;for (Node p = tail; p != node && p != null; p = p.prev)if (p.waitStatus <= 0)s = p;}if (s != null)LockSupport.unpark(s.thread);
}
  • unpark,唤醒park的节点;

然后唤醒之后还要去获取锁:

final boolean acquireQueued(final Node node, int arg) {boolean interrupted = false;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCreturn interrupted;}if (shouldParkAfterFailedAcquire(p, node))interrupted |= parkAndCheckInterrupt();}} catch (Throwable t) {cancelAcquire(node);if (interrupted)selfInterrupt();throw t;}
}
  • 还是在acquireQueued方法中,因为park之后阻塞在parkAndCheckInterrupt方法,unpark之后重新进入循环,这个时候再去tryAcquire就会成功,然后就会if中的逻辑,将当前节点设为头节点,当前节点2关联的线程也设置为空;
  • 原本的头节点会被垃圾回收;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

还有一种竞争失败情况,就是在阻塞队列中unpark之后的线程要将非公平锁的state改成1时,外部来了线程将state改为1了,就出现了竞争锁失败的情况;

final boolean acquireQueued(final Node node, int arg) {boolean interrupted = false;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCreturn interrupted;}if (shouldParkAfterFailedAcquire(p, node))interrupted |= parkAndCheckInterrupt();}} catch (Throwable t) {cancelAcquire(node);if (interrupted)selfInterrupt();throw t;}}

就是在这段代码中,parkAndCheckInterrupt结束阻塞,进入下一次循环时,tryAcquire失败,就是获取锁失败会继续等待;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3:可重入锁原理

@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {//获取当前线程final Thread current = Thread.currentThread();//获取锁的状态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) // overflowthrow new Error("Maximum lock count exceeded");//设置锁的状态setState(nextc);return true;}return false;
}

释放锁:

@ReservedStackAccess
protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;
}

一直减,直到减为0返回true;

4:可打断原理

  • 不可打断模式(默认是不可打断的)
  • 进入队列之后,被打断不会立即响应,只有获取锁之后才能反应,但也是继续运行,即打断标记为true;
(一)final boolean acquireQueued(final Node node, int arg) {boolean interrupted = false;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCreturn interrupted;}if (shouldParkAfterFailedAcquire(p, node))interrupted |= parkAndCheckInterrupt();//阻塞进入的方法}} catch (Throwable t) {cancelAcquire(node);if (interrupted)selfInterrupt();throw t;}
}
(二)private final boolean parkAndCheckInterrupt() {LockSupport.park(this);//此时阻塞//被打断后,会返回true,但是会被清除为true;return Thread.interrupted();}
(三)public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//返回的打断标记是true,就会进入if语句块中selfInterrupt();//会打断一下然后重新设置为true(表示之前被打断过)}
(四)static void selfInterrupt() {Thread.currentThread().interrupt();}

然后interrupted会被设置为true,直到某次循环获取锁之后会返回打断标记是true;

  • 可打断模式,直接抛出异常停止等待;
public final void acquireInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (!tryAcquire(arg))doAcquireInterruptibly(arg);//进入方法(二)
}
(二)private void doAcquireInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.EXCLUSIVE);try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCreturn;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())//如果被打断返回true,进入ifthrow new InterruptedException();//直接抛出异常停止等待}} catch (Throwable t) {cancelAcquire(node);throw t;}}

5:公平锁原理

  • 非公平锁(不管等待队列,如果没人占用锁直接占用)

    @ReservedStackAccess
    final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();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) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
    }
    
  • 公平锁(调用hasQueuedPredecessors方法)

 @ReservedStackAccessprotected 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;}
}
public final boolean hasQueuedPredecessors() {Node h, s;if ((h = head) != null) {if ((s = h.next) == null || s.waitStatus > 0) {s = null; // traverse in case of concurrent cancellationfor (Node p = tail; p != h && p != null; p = p.prev) {if (p.waitStatus <= 0)s = p;}}if (s != null && s.thread != Thread.currentThread())return true;}return false;
  • 如果队列中有多个节点,并且当前节点不是第二个节点就会返回true,这样的化!hasQueuedPredecessors就是false,不会进入后面的获取锁的方法;

6:条件变量

public final void await() throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();Node node = addConditionWaiter();int savedState = fullyRelease(node);int interruptMode = 0;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 cancelledunlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);
}

await方法,首先将当前线程加入到ConditionObject中,这里的等待队列也是一个双向链表,有头节点和尾节点

private Node addConditionWaiter() {if (!isHeldExclusively())throw new IllegalMonitorStateException();Node t = lastWaiter;// If lastWaiter is cancelled, clean out.if (t != null && t.waitStatus != Node.CONDITION) {unlinkCancelledWaiters();t = lastWaiter;}Node node = new Node(Node.CONDITION);if (t == null)firstWaiter = node;elset.nextWaiter = node;lastWaiter = node;return node;
}

这里将线程与节点关联,然后如果没有节点的话这个节点就是头尾节点,如果有节点的话加入到链表的末尾;

node的waitstatus是-2

然后就要释放锁,因为可能是可重入锁,所以要使用方法fullyRelease,将所有锁释放,将state置为0;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

final int fullyRelease(Node node) {try {int savedState = getState();if (release(savedState))return savedState;throw new IllegalMonitorStateException();} catch (Throwable t) {node.waitStatus = Node.CANCELLED;throw t;}
}

调用release方法,因为进入了condition中了所已需要唤醒后面的线程;

进入release方法,执行unparkSuccessor唤醒后面的线程;

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后咱们要park在condition的线程;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • signal唤醒流程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

thread-1要去唤醒thread-0,也是从condition中的第一个元素开始唤醒;

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

唤醒的操作主要有两个,一个从将该线程节点从condition中断开,然后将NOde的状态变成0,加入到AQS的队列中,然后将队列尾前的节点的状态设为-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 cannot change waitStatus, the node has been cancelled.*/if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))return false;/** Splice onto queue and try to set waitStatus of predecessor to* indicate that thread is (probably) waiting. If cancelled or* attempt to set waitStatus fails, wake up to resync (in which* case the waitStatus can be transiently and harmlessly wrong).*/Node p = enq(node);int ws = p.waitStatus;if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))LockSupport.unpark(node.thread);return true;
}

三:读写锁(reentrantreadwriteLock)

  • 当读操作远远大于写操作时,我们使用读写锁让读- 读可以并发执行,提高效率;
class DataContainer {private Object data;private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();private ReentrantReadWriteLock.ReadLock r = rw.readLock();private ReentrantReadWriteLock.WriteLock w = rw.writeLock();public Object read() {log.debug("获取读锁...");r.lock();try {log.debug("读取");sleep(1);return data;} finally {log.debug("释放读锁...");r.unlock();}}public void write() {log.debug("获取写锁...");w.lock();try {log.debug("写入");sleep(1);} finally {log.debug("释放写锁...");w.unlock();}}}

当两个线程进行读操作时没有互斥,一个读一个写,或者两个都是写操作的时候产生互斥;

注意事项:

1:读锁不支持条件变量

2:不支持锁升级,即以及索取了读锁不能再获取写锁,必须要释放了读锁之后才能获取写锁,否则就会永久等待;

3:可以锁降级,在获取写锁的情况下可以获取读锁;

class CachedData {Object data;// 是否有效,如果失效,需要重新计算 datavolatile boolean cacheValid;final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();void processCachedData() {rwl.readLock().lock();if (!cacheValid) {// 获取写锁前必须释放读锁rwl.readLock().unlock();rwl.writeLock().lock();try {// 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新if (!cacheValid) {data = ...cacheValid = true;}// 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存rwl.readLock().lock();} finally {rwl.writeLock().unlock();}}// 自己用完数据, 释放读锁 try {use(data);} finally {rwl.readLock().unlock();}}
}
应用之缓存

缓存更新策略:

先清空缓存再更新缓存:如果有两个线程,一个线程清空缓存之后,另一个线程来读取,发现缓存没有,于是去数据库查找,这个时候前面的线程还没完成数据的更新,于是查找的信息还是没有更新之前的,并且并入缓存,之后查询虽然数据库已经更改了,但是缓存中是旧数据;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果是先更新数据库,一个线程去更新数据库,另外一个线程查询旧数据,然后更新完数据库之后就清除缓存,旧数据也被清除,就可以重新建立缓存

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

还有一个概率非常小的情况:就是第一次建立缓存或者是缓存更新之后,建立缓存,会查询数据库,但是查询时间比较长,这个时候另一个线程修改了数据,并且清空了缓存之后,前面的线程查询到了缓存建立了缓存,这是旧的数据,就会造成缓存的不一致;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

class GenericCachedDao<T> {// HashMap 作为缓存非线程安全, 需要保护HashMap<SqlPair, T> map = new HashMap<>();ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); GenericDao genericDao = new GenericDao();public int update(String sql, Object... params) {SqlPair key = new SqlPair(sql, params);// 加写锁, 防止其它线程对缓存读取和更改lock.writeLock().lock();try {int rows = genericDao.update(sql, params);map.clear();return rows;} finally {lock.writeLock().unlock();}}public T queryOne(Class<T> beanClass, String sql, Object... params) {SqlPair key = new SqlPair(sql, params);// 加读锁, 防止其它线程对缓存更改lock.readLock().lock();try {T value = map.get(key);if (value != null) {return value;}} finally {lock.readLock().unlock();}// 加写锁, 防止其它线程对缓存读取和更改lock.writeLock().lock();try {// get 方法上面部分是可能多个线程进来的, 可能已经向缓存填充了数据// 为防止重复查询数据库, 再次验证T value = map.get(key);if (value == null) {// 如果没有, 查询数据库value = genericDao.queryOne(beanClass, sql, params);map.put(key, value);}return value;} finally {lock.writeLock().unlock();}}// 作为 key 保证其是不可变的class SqlPair {private String sql;private Object[] params;public SqlPair(String sql, Object[] params) {this.sql = sql;this.params = params;}@Overridepublic boolean equals(Object o) {if (this == o) {return true;}if (o == null || getClass() != o.getClass()) {return false;}SqlPair sqlPair = (SqlPair) o;return sql.equals(sqlPair.sql) &&Arrays.equals(params, sqlPair.params);}@Overridepublic int hashCode() {int result = Objects.hash(sql);result = 31 * result + Arrays.hashCode(params);return result;}}}
  • 修改数据库,更新缓存的位置加上写锁,查询数据库的位置加上读锁;
  • 然后在建立缓存时,是一个写锁,可能有多个线程进入但是只有一个线程进来,进来之后查询数据库建立缓存,释放锁,后续的线程进来还是会再次查询数据库,所以我们进行二次检测,判断缓存有没有前面的线程建立好,就不用再去查询数据库了;
补充
  • 适合读多写少的情况,当有大量的写操作时,性能会有影响;
  • 没有缓存容量
  • 没有缓存过期
  • 只适合单机
  • 只有一把锁,并发性还是不高,如果是有多张表应该配有多把锁;
  • 更新过于简单,直接把所以key删除,应该将key重新划分
原理-加写锁
  • 读写锁使用的是同一个sych同步器,因此等待队列和state用的都是同一个

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 读写锁加写锁的过程和reentrantlock加锁的原理一致,唯一不同的是,state分为两个部分,高16位是读锁,低16位是写锁;
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}
  • 首先进入acquire,然后尝试加锁;
@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {/** Walkthrough:* 1. If read count nonzero or write count nonzero*    and owner is a different thread, fail.* 2. If count would saturate, fail. (This can only*    happen if count is already nonzero.)* 3. Otherwise, this thread is eligible for lock if*    it is either a reentrant acquire or*    queue policy allows it. If so, update state*    and set owner.*/Thread current = Thread.currentThread();int c = getState();//获取当前的状态值int w = exclusiveCount(c);//获取低16位if (c != 0) {//c不等于0分为两种情况一个是加了读锁,一个是加了写锁// (Note: if c != 0 and w == 0 then shared count != 0)if (w == 0 || current != getExclusiveOwnerThread())//如果w==0说明加了读锁,读写互斥falsereturn false;//如果当前线程不等于owner线程说明不是重入就返回falseif (w + exclusiveCount(acquires) > MAX_COUNT)//加写锁,判断写锁不会超出16位的限制throw new Error("Maximum lock count exceeded");// Reentrant acquiresetState(c + acquires);//更新state的状态return true;}//如果c等于0if (writerShouldBlock() ||//这个方法非公平锁返回false,公平锁判断是否的等待队列的第二个节点!compareAndSetState(c, c + acquires))//尝试加锁,加锁失败进入ifreturn false;//返回falsesetExclusiveOwnerThread(current);//获取锁成功,将owner设为当前线程return true;
}
  • 然后进入tryacquire尝试加锁
原理-加读锁
public final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0)doAcquireShared(arg);
}
  • tryAcquireShared方法{-1-获取锁失败 。 0-获取锁成功。 正数- 获取锁成功,并且表示还有几个节点需要唤醒}

  • 加锁失败进入doAcquireShared方法,在这里就是加入等待队列的逻辑,首先是加入两个节点第一个是占位的,第二个就是读锁的节点,这里的不同是NODE是shared模式,不是ex模式,此时t2还处于活跃状态

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 然后如果节点是第二个节点还会再去尝试获取锁,获取锁失败进入下面的代码将前驱节点的waitstatus改为-1,然后再进入一次循环,再次尝试获取锁,获取锁失败就能park;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

private void doAcquireShared(int arg) {final Node node = addWaiter(Node.SHARED);boolean interrupted = false;try {for (;;) {final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCreturn;}}if (shouldParkAfterFailedAcquire(p, node))interrupted |= parkAndCheckInterrupt();}} catch (Throwable t) {cancelAcquire(node);throw t;} finally {if (interrupted)selfInterrupt();}
}
  • 后续t3加读锁,t4加写锁

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

原理-释放写锁

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 释放锁同样也是调用tryRelease,将owner置为空,同时也会唤醒等待队列中的第二个线程节点;

唤醒之后,再来一次循环,尝试给高16位加锁,加锁成功state的高位就会加一;

  • 然后进入setHeadAndPropagate方法,这个方法会将加锁成功的线程节点作为头节点,再setHeadAndPropagate方法内还会判断下一个节点是否也是读锁,如果也是的话就会唤醒;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 最后变成这样

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

原理-释放读锁
  • 使用unlock释放读锁,然后state会减一,最后返回是否==0,这里因为state=2,。state减一之后不等于0,返回flase;
  • 再次unlock释放t3;
  • 释放t3,这时state==0了,返回true,进入doReleaseShared方法,在这个方法中会将头节点的status设为0,并且将阻塞队列中的老二唤醒,这时没有其他线程竞争,t4获取到锁;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

相关文章:

J.U.C(1)

目录 JUC&#xff08;一&#xff09;一&#xff1a;AQS二&#xff1a;reentrantlock原理1&#xff1a;加锁&#xff1a;2&#xff1a;解锁3&#xff1a;可重入锁原理4&#xff1a;可打断原理5&#xff1a;公平锁原理6&#xff1a;条件变量 三&#xff1a;读写锁&#xff08;ree…...

计算机网络之---静态路由与动态路由

静态路由 静态路由是由网络管理员手动配置并固定的路由方式。路由器通过静态配置的路由条目来转发数据包&#xff0c;而不会自动调整。它不依赖于任何路由协议。 特点&#xff1a; 手动配置&#xff1a;网络管理员需要手动在路由器中配置每条静态路由。不自动更新&#xff1a;…...

Kubernetes1.28 编译 kubeadm修改证书有效期到 100年.并更新k8s集群证书

文章目录 前言一、资源准备1. 下载对应源码2.安装编译工具3.安装并设置golang 二、修改证书有效期1.修改证书有效期2.修改 CA 证书有效期 三、编译kubeadm四、使用新kubeadm方式1.当部署新集群时,使用该kubeadm进行初始化2.替换现有集群kubeadm操作 前言 kubeadm 默认证书为一…...

C++----STL(string)

引言&#xff1a;STL简介 什么是STL STL(standard template libaray-标准模板库)&#xff1a; 是 C标准库的重要组成部分&#xff08;注意&#xff1a;STL只是C标准库里的一部分&#xff0c;cin和cout也是属于C标准库的&#xff09;&#xff0c;不仅是一个可复用的组件库&…...

利用 Java 爬虫从 yiwugo 根据 ID 获取商品详情

在当今数字化时代&#xff0c;数据是商业决策的关键。对于从事国际贸易的商家来说&#xff0c;精准获取商品的详细信息至关重要。yiwugo 是一个知名的国际贸易平台&#xff0c;拥有海量的商品数据。通过 Java 爬虫技术&#xff0c;我们可以高效地从 yiwugo 根据商品 ID 获取详细…...

vue2修改表单只提交被修改的数据的字段传给后端接口

效果&#xff1a; 步骤一、 vue2修改表单提交的时候&#xff0c;只将修改的数据的字段传给后端接口&#xff0c;没有修改得数据不传参给接口。 在 data 对象中添加一个新的属性&#xff0c;用于存储初始表单数据的副本&#xff0c;与当前表单数据进行比较&#xff0c;找出哪些…...

Flink类加载机制详解

1. 总览 在运行Flink应用时,它会加载各种类,另外我们用户代码也会引入依赖,由于他们依赖版本以及加载顺序等不同,就可能会导致冲突,所以很要必要了解Flink是如何加载类的。 根据加载的来源的不同,我们可以将类分为三种: Java Classpath:Java类路径下,这是Java通用的…...

ClickHouse大数据准实时更新

一、问题背景 最近有一个项目需求&#xff0c;需要对日活跃的3万辆车的定位数据进行分析&#xff0c;并支持查询和统计分析结果。每辆车每天产生1条分析结果数据&#xff0c;要求能够查询过去一年内的所有分析结果。因此&#xff0c;每月需要处理约90万条记录&#xff0c;一年大…...

计算机网络之---端口与套接字

总括 端口&#xff1a;是计算机上用于标识网络服务的数字标识符&#xff0c;用于区分不同的服务或应用程序。套接字&#xff1a;是操作系统提供的用于进程间网络通信的编程接口&#xff0c;允许程序通过它来进行数据的发送、接收和连接管理。关系&#xff1a;端口号用于标识服…...

UE5中制作地形材质

在创作大场景内容时&#xff0c;地形的设计和优化是至关重要的一步。利用UE中的地形系统&#xff0c;开发者能够高效地创建复杂的地形形态&#xff0c;同时保持游戏的性能和视觉效果。 1.在创建地形之前&#xff0c;先新建一个地形使用的混合材质球&#xff0c;添加节点Landsc…...

【Docker】docker compose 安装 Redis Stack

注&#xff1a;整理不易&#xff0c;请不要吝啬你的赞和收藏。 前文 Redis Stack 什么是&#xff1f; 简单来说&#xff0c;Redis Stack 是增强版的 Redis &#xff0c;它在传统的 Redis 数据库基础上增加了一些高级功能和模块&#xff0c;以支持更多的使用场景和需求。Redis…...

pytest 常用插件

pytest 提供了许多功能强大的插件来增强测试体验和执行能力。以下是一些常用的 pytest 插件介绍&#xff0c;并结合 pytest.main() 进行使用的示例。 1. pytest-xdist pytest-xdist 插件用于并行化测试的执行&#xff0c;可以将测试分配到多个 CPU 核心并行运行&#xff0c;从…...

浅谈云计算05 | 云存储等级及其接口工作原理

一、云存储设备 在当今数字化飞速发展的时代&#xff0c;数据已然成为个人、企业乃至整个社会的核心资产。从日常生活中的珍贵照片、视频&#xff0c;到企业运营里的关键业务文档、客户资料&#xff0c;数据量呈爆炸式增长。面对海量的数据&#xff0c;如何安全、高效且便捷地存…...

linux:文件的创建/删除/复制/移动/查看/查找/权限/类型/压缩/打包,文本处理sed,awk

关于文件的关键词 创建 touch 删除 rm 复制 cp 权限 chmod 移动 mv 查看内容 cat(全部); head(前10行); tail(末尾10行); more,less,grep 查找 find 压缩 gzip ; bzip 打包 tar 编辑 sed 文本处理 awk 创建文件 格式&#xff1a; touch 文件名 删除文件 复制文…...

CentOS 8 如何安装java与mysql

在CentOS 8上安装Java和MySQL的步骤如下&#xff1a; 1. 安装 Java 1.1 安装 OpenJDK&#xff08;推荐&#xff09; CentOS 8 默认的软件仓库提供了 OpenJDK 包&#xff0c;您可以直接使用 dnf 命令安装。 # 更新系统 sudo dnf update -y# 安装 OpenJDK 11&#xff08;Cent…...

Go语言之路————go基本语法、数据类型、变量、常量、输出

Go语言之路————go基本语法、数据类型、变量、常量 前言一、基本语法知识二、数据类型三、常量四、变量五、作用域六、输入输出 前言 我是一名多年Java开发人员&#xff0c;因为工作需要现在要学习go语言&#xff0c;Go语言之路是一个系列&#xff0c;记录着我从0开始接触G…...

音视频入门基础:MPEG2-PS专题(7)——通过FFprobe显示PS流每个packet的信息

音视频入门基础&#xff1a;MPEG2-PS专题系列文章&#xff1a; 音视频入门基础&#xff1a;MPEG2-PS专题&#xff08;1&#xff09;——MPEG2-PS官方文档下载 音视频入门基础&#xff1a;MPEG2-PS专题&#xff08;2&#xff09;——使用FFmpeg命令生成ps文件 音视频入门基础…...

Docker安装和卸载(centos)

Docker安装和卸载 一&#xff0c;已安装Docker&#xff0c;卸载Docker 1.方法一 sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine​ 如果出现以下提示就证明没卸载…...

YOLOv8从菜鸟到精通(二):YOLOv8数据标注以及模型训练

数据标注 前期准备 先打开Anaconda Navigator&#xff0c;点击Environment&#xff0c;再点击new(new是我下载anaconda的文件夹名称)&#xff0c;然后点击创建 点击绿色按钮&#xff0c;并点击Open Terminal 输入labelimg便可打开它,labelimg是图像标注工具&#xff0c;在上篇…...

Winforms开发基础之非主线程操作UI控件的误区

前言 想象一下&#xff0c;你正在开发一个桌面应用程序&#xff0c;用户点击按钮后需要执行一个耗时操作。为了避免界面卡顿&#xff0c;你决定使用后台线程来处理任务。然而&#xff0c;当你在后台线程中尝试更新UI控件时&#xff0c;程序突然崩溃了。这是为什么呢&#xff1…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...