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(一)一:AQS二:reentrantlock原理1:加锁:2:解锁3:可重入锁原理4:可打断原理5:公平锁原理6:条件变量 三:读写锁(ree…...
计算机网络之---静态路由与动态路由
静态路由 静态路由是由网络管理员手动配置并固定的路由方式。路由器通过静态配置的路由条目来转发数据包,而不会自动调整。它不依赖于任何路由协议。 特点: 手动配置:网络管理员需要手动在路由器中配置每条静态路由。不自动更新:…...
Kubernetes1.28 编译 kubeadm修改证书有效期到 100年.并更新k8s集群证书
文章目录 前言一、资源准备1. 下载对应源码2.安装编译工具3.安装并设置golang 二、修改证书有效期1.修改证书有效期2.修改 CA 证书有效期 三、编译kubeadm四、使用新kubeadm方式1.当部署新集群时,使用该kubeadm进行初始化2.替换现有集群kubeadm操作 前言 kubeadm 默认证书为一…...
C++----STL(string)
引言:STL简介 什么是STL STL(standard template libaray-标准模板库): 是 C标准库的重要组成部分(注意:STL只是C标准库里的一部分,cin和cout也是属于C标准库的),不仅是一个可复用的组件库&…...
利用 Java 爬虫从 yiwugo 根据 ID 获取商品详情
在当今数字化时代,数据是商业决策的关键。对于从事国际贸易的商家来说,精准获取商品的详细信息至关重要。yiwugo 是一个知名的国际贸易平台,拥有海量的商品数据。通过 Java 爬虫技术,我们可以高效地从 yiwugo 根据商品 ID 获取详细…...
vue2修改表单只提交被修改的数据的字段传给后端接口
效果: 步骤一、 vue2修改表单提交的时候,只将修改的数据的字段传给后端接口,没有修改得数据不传参给接口。 在 data 对象中添加一个新的属性,用于存储初始表单数据的副本,与当前表单数据进行比较,找出哪些…...
Flink类加载机制详解
1. 总览 在运行Flink应用时,它会加载各种类,另外我们用户代码也会引入依赖,由于他们依赖版本以及加载顺序等不同,就可能会导致冲突,所以很要必要了解Flink是如何加载类的。 根据加载的来源的不同,我们可以将类分为三种: Java Classpath:Java类路径下,这是Java通用的…...
ClickHouse大数据准实时更新
一、问题背景 最近有一个项目需求,需要对日活跃的3万辆车的定位数据进行分析,并支持查询和统计分析结果。每辆车每天产生1条分析结果数据,要求能够查询过去一年内的所有分析结果。因此,每月需要处理约90万条记录,一年大…...
计算机网络之---端口与套接字
总括 端口:是计算机上用于标识网络服务的数字标识符,用于区分不同的服务或应用程序。套接字:是操作系统提供的用于进程间网络通信的编程接口,允许程序通过它来进行数据的发送、接收和连接管理。关系:端口号用于标识服…...
UE5中制作地形材质
在创作大场景内容时,地形的设计和优化是至关重要的一步。利用UE中的地形系统,开发者能够高效地创建复杂的地形形态,同时保持游戏的性能和视觉效果。 1.在创建地形之前,先新建一个地形使用的混合材质球,添加节点Landsc…...
【Docker】docker compose 安装 Redis Stack
注:整理不易,请不要吝啬你的赞和收藏。 前文 Redis Stack 什么是? 简单来说,Redis Stack 是增强版的 Redis ,它在传统的 Redis 数据库基础上增加了一些高级功能和模块,以支持更多的使用场景和需求。Redis…...
pytest 常用插件
pytest 提供了许多功能强大的插件来增强测试体验和执行能力。以下是一些常用的 pytest 插件介绍,并结合 pytest.main() 进行使用的示例。 1. pytest-xdist pytest-xdist 插件用于并行化测试的执行,可以将测试分配到多个 CPU 核心并行运行,从…...
浅谈云计算05 | 云存储等级及其接口工作原理
一、云存储设备 在当今数字化飞速发展的时代,数据已然成为个人、企业乃至整个社会的核心资产。从日常生活中的珍贵照片、视频,到企业运营里的关键业务文档、客户资料,数据量呈爆炸式增长。面对海量的数据,如何安全、高效且便捷地存…...
linux:文件的创建/删除/复制/移动/查看/查找/权限/类型/压缩/打包,文本处理sed,awk
关于文件的关键词 创建 touch 删除 rm 复制 cp 权限 chmod 移动 mv 查看内容 cat(全部); head(前10行); tail(末尾10行); more,less,grep 查找 find 压缩 gzip ; bzip 打包 tar 编辑 sed 文本处理 awk 创建文件 格式: touch 文件名 删除文件 复制文…...
CentOS 8 如何安装java与mysql
在CentOS 8上安装Java和MySQL的步骤如下: 1. 安装 Java 1.1 安装 OpenJDK(推荐) CentOS 8 默认的软件仓库提供了 OpenJDK 包,您可以直接使用 dnf 命令安装。 # 更新系统 sudo dnf update -y# 安装 OpenJDK 11(Cent…...
Go语言之路————go基本语法、数据类型、变量、常量、输出
Go语言之路————go基本语法、数据类型、变量、常量 前言一、基本语法知识二、数据类型三、常量四、变量五、作用域六、输入输出 前言 我是一名多年Java开发人员,因为工作需要现在要学习go语言,Go语言之路是一个系列,记录着我从0开始接触G…...
音视频入门基础:MPEG2-PS专题(7)——通过FFprobe显示PS流每个packet的信息
音视频入门基础:MPEG2-PS专题系列文章: 音视频入门基础:MPEG2-PS专题(1)——MPEG2-PS官方文档下载 音视频入门基础:MPEG2-PS专题(2)——使用FFmpeg命令生成ps文件 音视频入门基础…...
Docker安装和卸载(centos)
Docker安装和卸载 一,已安装Docker,卸载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,点击Environment,再点击new(new是我下载anaconda的文件夹名称),然后点击创建 点击绿色按钮,并点击Open Terminal 输入labelimg便可打开它,labelimg是图像标注工具,在上篇…...
Winforms开发基础之非主线程操作UI控件的误区
前言 想象一下,你正在开发一个桌面应用程序,用户点击按钮后需要执行一个耗时操作。为了避免界面卡顿,你决定使用后台线程来处理任务。然而,当你在后台线程中尝试更新UI控件时,程序突然崩溃了。这是为什么呢࿱…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...
