Java AQS 阻塞式锁和相关同步器工具的框架

2023-12-22 03:44

本文主要是介绍Java AQS 阻塞式锁和相关同步器工具的框架,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

8 J.U.C

Java 并发工具包

AQS 原理

AQS:AbstractQueuedSynchronizer(抽象队列同步器),阻塞式锁和相关同步器工具的框架

特点:

  • 用 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)) {// 让阻塞线程恢复运行
}

实现不可重入锁

package com.rainsun.d7_thread_pool;import lombok.extern.slf4j.Slf4j;import java.net.MulticastSocket;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;@Slf4j(topic = "c.d3_TestAqs")
public class d3_TestAqs {public static void main(String[] args) {MyLock lock = new MyLock();new Thread(()->{lock.lock();try {log.debug("locking...");Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {log.debug("unlocking...");lock.unlock();}}, "t1").start();new Thread(()->{lock.lock();try {log.debug("locking...");} finally {log.debug("unlocking...");lock.unlock();}}, "t2").start();}
}// 自定义锁(不可重入锁)
class MyLock implements Lock {private MySync sync = new MySync();class MySync extends AbstractQueuedSynchronizer{@Override // 独占锁protected boolean tryAcquire(int arg) {if(compareAndSetState(0, 1)){// 加上了锁,设置 owner 为当前线程setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}@Overrideprotected boolean tryRelease(int arg) {setExclusiveOwnerThread(null);setState(0);return true;}@Override // 是否持有独占锁protected boolean isHeldExclusively() {return getState() == 1;}public Condition newCondition(){return new ConditionObject();}}@Overridepublic void lock() {sync.acquire(1);}@Overridepublic void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}@Overridepublic boolean tryLock() {return sync.tryAcquire(1);}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(time));}@Overridepublic void unlock() {sync.release(1);}@Overridepublic Condition newCondition() {return sync.newCondition();}
}
17:19:48 [t1] c.d3_TestAqs - locking...
17:19:49 [t1] c.d3_TestAqs - unlocking...
17:19:49 [t2] c.d3_TestAqs - locking...
17:19:49 [t2] c.d3_TestAqs - unlocking...

ReentrantLock 原理

image-20231221094204968

ReentrantLock 实现了 Lock 接口,内部有一个 Sync 的类继承自 AbstractQueuedSynchronizer (AQS),内部还有两个锁实现非公平锁和公平锁都继承了 Sync 类

非公平锁实现原理

默认创建非公平锁,当没有竞争时:执行的线程为当前线程

final boolean initialTryLock() {Thread current = Thread.currentThread();if (compareAndSetState(0, 1)) { // first attempt is unguardedsetExclusiveOwnerThread(current);return true;} else if (getExclusiveOwnerThread() == current) {int c = getState() + 1;if (c < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(c);return true;} elsereturn false;
}

image-20231221095442890

第一个竞争出现时:CAS 修改 state 失败,进入FIFO队列等待

image-20231221095601204

Thread-1 执行了

  1. CAS 尝试将 state 由 0 改为 1,结果失败

    @ReservedStackAccess
    final void lock() {if (!initialTryLock())acquire(1);
    }
    
  2. 进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败

    public final void acquire(int arg) {if (!tryAcquire(arg))acquire(null, arg, false, false, false, 0L);
    }protected final boolean tryAcquire(int acquires) {if (getState() == 0 && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;
    }
    
  3. 接下来进入 addWaiter 逻辑,将 Thread 封装为Node,构造 Node 队列

  • 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态
  • Node 的创建是懒惰的
  • 其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程

image-20231221095650779

当前线程进入 acquireQueued 逻辑

  1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
  2. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
  3. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false

image-20231221095936151

  1. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
  2. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回 true
  3. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

image-20231221102544145

再次有多个线程经历上述过程竞争失败,变成这个样子

image-20231221102558350

Thread-0 释放锁,进入 tryRelease 流程,如果成功

  • 设置 exclusiveOwnerThread 为 null
  • state = 0

image-20231221102621825

当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程
找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
回到 Thread-1 的 acquireQueued 流程

image-20231221102655249

如果加锁成功(没有竞争),会设置

  • exclusiveOwnerThread 为 Thread-1,state = 1
  • head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
  • 原本的 head 因为从链表断开,而可被垃圾回收

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了

如果不巧又被 Thread-4 占了先

  • Thread-4 被设置为 exclusiveOwnerThread,state = 1
  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

可重入原理

加锁时,state 加1:

final boolean initialTryLock() {Thread current = Thread.currentThread();if (compareAndSetState(0, 1)) { // first attempt is unguardedsetExclusiveOwnerThread(current);return true;} else if (getExclusiveOwnerThread() == current) {int c = getState() + 1;if (c < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(c);return true;} elsereturn false;
}

getExclusiveOwnerThread() == current 表示当前线程已经获得锁了

这时会 int c = getState() + 1; state 的值加1

解锁时,state 减 1:

@ReservedStackAccess
protected final boolean tryRelease(int releases) {int c = getState() - releases; // state - 1if (getExclusiveOwnerThread() != Thread.currentThread())throw new IllegalMonitorStateException();boolean free = (c == 0); // 只有 state 等于 0 才会 free,释放锁if (free)setExclusiveOwnerThread(null);setState(c);return free;
}

可打断原理

不可打断模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了

final int acquire(Node node, int arg, boolean shared,boolean interruptible, boolean timed, long time) {Thread current = Thread.currentThread();// ...for (;;) {Node t;if ((t = tail) == null) {// ...} else {long nanos;spins = postSpins = (byte)((postSpins << 1) | 1);if (!timed)LockSupport.park(this);else if ((nanos = time - System.nanoTime()) > 0L)LockSupport.parkNanos(this, nanos);elsebreak;node.clearStatus();if ((interrupted |= Thread.interrupted()) && interruptible) // Thread.interrupted()打断了,但是如果interruptible为false 并不会 break 停止,只将 interrupted标记 置为 true 了break;}}return cancelAcquire(node, interrupted, interruptible);
}private int cancelAcquire(Node node, boolean interrupted,boolean interruptible) {if (node != null) {node.waiter = null;node.status = CANCELLED;if (node.prev != null)cleanQueue();}if (interrupted) {if (interruptible)return CANCELLED;elseThread.currentThread().interrupt();}return 0;
}

公平锁实现原理

final boolean initialTryLock() {Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 队列中没有元素才会竞争if (!hasQueuedThreads() && compareAndSetState(0, 1)) {setExclusiveOwnerThread(current);return true;}} else if (getExclusiveOwnerThread() == current) {if (++c < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(c);return true;}return false;
}/*** Acquires only if thread is first waiter or empty*/
protected final boolean tryAcquire(int acquires) {// hasQueuedPredecessors 会检查当前 AQS 队列是否有前驱节点,没有才会竞争if (getState() == 0 && !hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;
}

读写锁

ReentrantReadWriteLock

当读操作远远高于写操作时,这时候使用 读写锁 让 读-读 可以并发,提高性能。 类似于数据库中的 select … from … lock in share mode

提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法

package com.rainsun.d8_JUC;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantReadWriteLock;@Slf4j(topic = "c.d2_ReadWriteLockTest")
public class d2_ReadWriteLockTest {public static void main(String[] args) throws InterruptedException {DataContainer dataContainer = new DataContainer();new Thread(()->{dataContainer.read();},"t1").start();Thread.sleep(100);new Thread(()->{dataContainer.write();},"t2").start();}
}@Slf4j(topic = "c.DataContainer")
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("get read lock ...");r.lock();try {log.debug("read");Thread.sleep(1000);return data;} catch (InterruptedException e) {throw new RuntimeException(e);} finally {r.unlock();log.debug("release read lock...");}}public void write(){log.debug("get write lock");w.lock();try {log.debug("write");}finally {log.debug("release write lock");w.lock();}}
}
11:22:49 [t1] c.DataContainer - get read lock ...
11:22:49 [t1] c.DataContainer - read
11:22:49 [t2] c.DataContainer - get write lock
11:22:50 [t1] c.DataContainer - release read lock...
11:22:50 [t2] c.DataContainer - write
11:22:50 [t2] c.DataContainer - release write lock

读完才能写,读读是并发的

注意事项

  • 读锁不支持条件变量
  • 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待

升级:读锁里包含写锁,则写锁会一直等待读完,但是读在等待写完

r.lock();
try {// ...w.lock();try {// ...} finally{w.unlock();}
} finally{r.unlock();
}
  • 重入时降级支持:即持有写锁的情况下去获取读锁

写锁后,在内部加上读锁,这样当写锁释放的时候,就获得了读锁。就能保证自己写的数据不会被其他线程获得写锁而篡改(因为我获取了读锁,与其他线程的写锁互斥,其他线程的写锁就不能改变我刚改的值)

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

读写锁原理

读写锁用的是同一个 Sycn 同步器,因此等待队列、state 等也是同一个

不同的是,state 中的低 16 为用于给写锁状态,高 16 用于给读锁状态计数

加锁原理

(1)t1 w.lock

t1 成功上锁,流程与 ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了 state 的低 16 位,而读锁使用的是 state 的高 16 位。

即 state 低 16 位为 1,ownerThread 为当前的 t1 线程

image-20231221150834559

(2)t2. r.lock

t2 执行 r.lock,这时进入读锁的 sync.acquireShared(1) 流程,首先会进入 tryAcquireShared 流程。如果有写锁占据,那么 tryAcquireShared 返回 -1 表示失败

public final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0)acquire(null, arg, true, false, false, 0L);
}
@ReservedStackAccess
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);if (!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT)) {// ...return 1;}return fullTryAcquireShared(current);
}

3)这时会进入 sync.doAcquireShared(1) 流程,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为 Node.SHARED 模式而非 Node.EXCLUSIVE 模式,注意此时 t2 仍处于活跃状态

image-20231221153058067

4)t2 会看看自己的节点是不是老二,如果是,还会再次调用 tryAcquireShared(1) 来尝试获取锁

5)如果没有成功,在 doAcquireShared 内 for (;😉 循环一次,把前驱节点的 waitStatus 改为 -1,再 for (;😉 循环一次尝试 tryAcquireShared(1) 如果还不成功,那么在 parkAndCheckInterrupt() 处 park

image-20231221153109887

(6)t3 r.lock,t4 w.lock

这种状态下,假设又有 t3 加读锁和 t4 加写锁,这期间 t1 仍然持有锁,就变成了下面的样子

image-20231221153154330

解锁原理

t1 w.unlock:

这时会走到写锁的 sync.release(1) 流程,调用 sync.tryRelease(1) 成功,变成下面的样子

image-20231221153258083

接下来执行唤醒流程 sync.unparkSuccessor,即让老二恢复运行,这时 t2 在 doAcquireShared 内 parkAndCheckInterrupt() 处恢复运行

这回再来一次 for (;😉 执行 tryAcquireShared 成功则让读锁计数加一

这时 t2 已经恢复运行,接下来 t2 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点

image-20231221153410110

事情还没完,在 setHeadAndPropagate 方法内还会检查下一个节点是否是 shared,如果是则调用 doReleaseShared() 将 head 的状态从 -1 改为 0 并唤醒老二,这时 t3 在 doAcquireShared 内parkAndCheckInterrupt() 处恢复运行

image-20231221153435614

这回再来一次 for (;😉 执行 tryAcquireShared 成功则让读锁计数加一

这时 t3 已经恢复运行,接下来 t3 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点

image-20231221153516742

下一个节点不是 shared 了,因此不会继续唤醒 t4 所在节点

t2 r.unlock,t3 r.unlock

t2 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,但由于计数还不为零

image-20231221153600513

t3 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,这回计数为零了,进入 doReleaseShared() 将头节点从 -1 改为 0 并唤醒老二,即

image-20231221153618984

之后 t4 在 acquireQueued 中 parkAndCheckInterrupt 处恢复运行,再次 for (;😉 这次自己是老二,并且没有其他 竞争,tryAcquire(1) 成功,修改头结点,流程结束

image-20231221153631882

StampedLock 配合戳使用的读写锁

该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用

乐观读,StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次 戳校验 如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全

提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法

Semaphore 信号量

Semaphore 用来限制能同时访问共享资源的线程上限。

@Slf4j(topic = "c.d3_SemaphoreTest")
public class d3_SemaphoreTest {public static void main(String[] args) {// 1. 创建 semaphore 对象Semaphore semaphore = new Semaphore(3);// 2. 10 线程同时执行for (int i = 0; i < 10; i++) {new Thread(()->{// 3. acquire 获得许可try {semaphore.acquire();} catch (InterruptedException e) {throw new RuntimeException(e);}try {log.debug("running ... ");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}log.debug("end...");} finally {// 4. 释放许可semaphore.release();}}).start();}}
}

Semaphore 原理

Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就像是获得了停车位,然后停车场显示空余车位减一

刚开始,permits(state)为 3,这时 5 个线程来获取资源

image-20231221170806944

假设其中 Thread-1,Thread-2,Thread-4 cas 竞争成功,而 Thread-0 和 Thread-3 竞争失败,进入 AQS 队列 park 阻塞

image-20231221170702951

这时 Thread-4 释放了 permits,接下来 Thread-0 竞争成功,permits 再次设置为 0,设置自己为 head 节点,断开原来的 head 节点,unpark 接下来的 Thread-3 节点,但由于 permits 是 0,因此 Thread-3 在尝试不成功后再次进入 park 状态

image-20231221170754665

CountdownLatch 倒计时锁

用来进行线程同步协作,等待所有线程完成倒计时。

其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一

@Slf4j(topic = "c.d4_CountDownLatchTest")
public class d4_CountDownLatchTest {public static void main(String[] args) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(3);new Thread(()->{log.debug("begin...");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}countDownLatch.countDown();log.debug("end...");}).start();new Thread(()->{log.debug("begin...");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}countDownLatch.countDown();log.debug("end...");}).start();new Thread(()->{log.debug("begin...");try {Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}countDownLatch.countDown();log.debug("end...");}).start();log.debug("waiting...");countDownLatch.await();log.debug("waiting end...");}
}

配合线程池使用:

@Slf4j(topic = "c.d4_CountDownLatchTest")
public class d4_CountDownLatchTest {public static void main(String[] args) throws InterruptedException {ExecutorService service = Executors.newFixedThreadPool(4);CountDownLatch countDownLatch = new CountDownLatch(3);service.submit(()->{log.debug("begin...");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}countDownLatch.countDown();log.debug("end...{}", countDownLatch.getCount());});service.submit(()->{log.debug("begin...");try {Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}countDownLatch.countDown();log.debug("end...{}", countDownLatch.getCount());});service.submit(()->{log.debug("begin...");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}countDownLatch.countDown();log.debug("end...{}", countDownLatch.getCount());});service.submit(()->{log.debug("waiting...");try {countDownLatch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}log.debug("waiting end...");});}
}
16:38:58 [pool-1-thread-4] c.d4_CountDownLatchTest - waiting...
16:38:58 [pool-1-thread-2] c.d4_CountDownLatchTest - begin...
16:38:58 [pool-1-thread-1] c.d4_CountDownLatchTest - begin...
16:38:58 [pool-1-thread-3] c.d4_CountDownLatchTest - begin...
16:38:59 [pool-1-thread-1] c.d4_CountDownLatchTest - end...2
16:38:59 [pool-1-thread-2] c.d4_CountDownLatchTest - end...1
16:39:00 [pool-1-thread-3] c.d4_CountDownLatchTest - end...0
16:39:00 [pool-1-thread-4] c.d4_CountDownLatchTest - waiting end...

等待其他线程完成的 demo:

public class d5_CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {ExecutorService service = Executors.newFixedThreadPool(10);CountDownLatch latch = new CountDownLatch(10);String[] allstate = new String[10];for(int i = 0; i < 10;  ++ i){int finalI = i;new Thread(()->{for(int load = 0; load <= 100; ++ load){allstate[finalI] = load + "%";System.out.print("\r" + Arrays.toString(allstate));}latch.countDown();}).start();}latch.await();System.out.println("\nGame begin !");service.shutdown();}
}

CyclicBarrier 同步屏障

循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,每等待一个线程计数个数就加1

当等待的线程数满足『计数个数』时,即最后一个线程到达同步点的屏障时,屏障解除,线程继续执行

CyclicBarrier 与 CountDownLatch 的主要区别在于 CyclicBarrier 是可以重用的

CyclicBarrier 可以被比喻为『人满发车』,没坐满,前面已上车的线程就需要继续等待

@Slf4j(topic = "c.d6_CyclicBarrierTest")
public class d6_CyclicBarrierTest {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(2);CyclicBarrier barrier = new CyclicBarrier(2, ()->{log.debug("task1 task2 finish...");});for (int i = 0; i < 3; i++) {service.submit(()->{log.debug("task1 begin...");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}try {// 初始计数-1barrier.await();log.debug("task1 end...");} catch (Exception e) {e.printStackTrace();}});service.submit(()->{log.debug("task2 begin...");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}try {// 初始计数-1barrier.await();log.debug("task2 end...");} catch (Exception e) {e.printStackTrace();}});}service.shutdown();}
}

这篇关于Java AQS 阻塞式锁和相关同步器工具的框架的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/522465

相关文章

springboot健康检查监控全过程

《springboot健康检查监控全过程》文章介绍了SpringBoot如何使用Actuator和Micrometer进行健康检查和监控,通过配置和自定义健康指示器,开发者可以实时监控应用组件的状态,... 目录1. 引言重要性2. 配置Spring Boot ActuatorSpring Boot Act

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

java如何分布式锁实现和选型

《java如何分布式锁实现和选型》文章介绍了分布式锁的重要性以及在分布式系统中常见的问题和需求,它详细阐述了如何使用分布式锁来确保数据的一致性和系统的高可用性,文章还提供了基于数据库、Redis和Zo... 目录引言:分布式锁的重要性与分布式系统中的常见问题和需求分布式锁的重要性分布式系统中常见的问题和需求

SpringBoot基于MyBatis-Plus实现Lambda Query查询的示例代码

《SpringBoot基于MyBatis-Plus实现LambdaQuery查询的示例代码》MyBatis-Plus是MyBatis的增强工具,简化了数据库操作,并提高了开发效率,它提供了多种查询方... 目录引言基础环境配置依赖配置(Maven)application.yml 配置表结构设计demo_st

在Ubuntu上部署SpringBoot应用的操作步骤

《在Ubuntu上部署SpringBoot应用的操作步骤》随着云计算和容器化技术的普及,Linux服务器已成为部署Web应用程序的主流平台之一,Java作为一种跨平台的编程语言,具有广泛的应用场景,本... 目录一、部署准备二、安装 Java 环境1. 安装 JDK2. 验证 Java 安装三、安装 mys

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

SpringCloud集成AlloyDB的示例代码

《SpringCloud集成AlloyDB的示例代码》AlloyDB是GoogleCloud提供的一种高度可扩展、强性能的关系型数据库服务,它兼容PostgreSQL,并提供了更快的查询性能... 目录1.AlloyDBjavascript是什么?AlloyDB 的工作原理2.搭建测试环境3.代码工程1.

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python