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控件时,程序突然崩溃了。这是为什么呢࿱…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

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

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...