Java多线程之CountDownLatch同步器的使用(六)

2024-05-26 12:08

本文主要是介绍Java多线程之CountDownLatch同步器的使用(六),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

3-4、CountDownLatch:同步器

在JKD1.5+环境中,Doug Lea和他的团队为我们提供了可以很好实现这个要求的工具类:CountDownLatch和CyclicBarrier。我们首先介绍CountDownLatch的基本使用方式:

3-4-1、CountDownLatch基本使用

CountDownLatch是一个同步计数器,能够保证在其他线程完成某一个业务操作前,当前线程一直处于等待/阻塞状态。具体来说,这个计数器将会从给定的某一个数值count开始,通过countDown()方法的调用进行倒数。当执行某一次countDown()操作后,计数器的count数值等于0,所有调用了await()方法的线程,就解除等待/阻塞状态继续执行。我们来看一段简单的示例代码:

package test.thread.countDownLatch;import java.util.concurrent.CountDownLatch;import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.BasicConfigurator;public class TestCountDownLatch {/*** 日志*/private static Log LOGGER = LogFactory.getLog(TestCountDownLatch.class);static {BasicConfigurator.configure();}public static void main(String[] args) throws Throwable {// 同步计数器从5开始计数final CountDownLatch countDownLatch = new CountDownLatch(5);// 启动子线程,处理“其他”业务for(int index = 0 ; index < 5 ; index++) {Thread childThread = new Thread() {@Overridepublic void run() {//等待,以便模型业务处理过程消耗的时间synchronized (this) {try {this.wait(1000);} catch (InterruptedException e) {TestCountDownLatch.LOGGER.error(e.getMessage(), e);}}// 完成业务处理过程,计数器-1long threadid = Thread.currentThread().getId();TestCountDownLatch.LOGGER.info("子线程(" + threadid + ")执行完成!");countDownLatch.countDown();}};childThread.start();}// 等待所有子线程的业务都处理完成(计数器的count为0时)countDownLatch.await();TestCountDownLatch.LOGGER.info("所有子线程的处理都完了,主线程继续执行...");}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

以下是可能的执行结果(因为每次执行的效果可能有所区别):

1 [Thread-3] INFO test.thread.countDownLatch.TestCountDownLatch  - 子线程(12)执行完成!
1 [Thread-4] INFO test.thread.countDownLatch.TestCountDownLatch  - 子线程(13)执行完成!
1 [Thread-1] INFO test.thread.countDownLatch.TestCountDownLatch  - 子线程(10)执行完成!
1 [Thread-2] INFO test.thread.countDownLatch.TestCountDownLatch  - 子线程(11)执行完成!
1 [Thread-0] INFO test.thread.countDownLatch.TestCountDownLatch  - 子线程(9)执行完成!
9 [main] INFO test.thread.countDownLatch.TestCountDownLatch  - 所有子线程的处理都完了,主线程继续执行...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

以上代码片段和执行结果说明了CountDownLatch的最简单使用,CountDownLatch同步计数器从5开始计数,分别对应5个子线程的业务完成情况。每当一个子线程业务完成后,CountDownLatch同步计数器就countDown一次。直到count等于0时,这时主线程上面的await()方法解除等待/阻塞状态,继续执行。这里要注意一下:

  • 不是说只能有一次await方法的调用,而是同一时间可以有多个线程调用了await方法。只要在count还不等于0时,某个线程调用了await方法,它都会进入等待/阻塞状态。

  • 在调用await时,如果CountDownLatch同步计数器的count已经等于0了,则await方法不会进入等待/阻塞状态。

  • await调用和countDown调用不是说必须处于不同线程。同一线程中,您可以先调用countDown然后再调用await进入等待/阻塞。CountDownLatch同步计数器会始终遵循上两条工作原则。

  • 在使用CountDownLatch同步计数器时,您无需考虑脏数据的问题。CountDownLatch同步计数器是线程安全的。

3-4-2、CountDownLatch在“100米赛跑”中的应用

很明显CountDownLatch在“100米赛跑”中的使用目标是:“等待这组所有的选手全部上跑道”,然后一起开始跑步。所以,CountDownLatch的计数器,需要在选手获得“跑道”资源后,马上countDown一次。之后,获得“跑道”资源的选手要立刻调用await进入等待状态,等待其他选手也获得跑道资源。我们给这整个处理逻辑去一个名字叫做:“发令枪”,代码片段如下:

......
/*** 选手所关注的发令枪*/
private CountDownLatch startingGun;
......public Result call() throws Exception {......try {// 申请上跑道(这个没有变化)this.runway.acquire();// 等待可能的发令枪if(this.startingGun != null) {// 执行到这里,说明这个选手已经拿到了跑到资源;// 向发令枪表达“我已准备好”,即计数器-1this.startingGun.countDown();System.out.println("选手" + name + "[" + number + "],已登上跑道,等待发令!");// 接下来进入“等待”状态// 以便等这个发令枪所管理的所有选手上跑道了,再一起跑步this.startingGun.await();System.out.println("选手" + name + "[" + number + "],跑!");}// 开始正式跑步return this.result = this.doRun();} catch(Exception e) {e.printStackTrace(System.out);} finally {// 都要进入初赛结果排序(中途退赛的成绩就为0)this.runway.release();System.out.println("选手" + name + "[" + number + "],比赛正常完成!");}......
}
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

那么如何进行分组呢?我们可以让一组选手,关注同一把“发令枪”(有多少把发令枪,就有多少个组、就有多少个CountDownLatch对象)。如下图所示:

这里写图片描述

另外,分组时一定要考虑一个问题:由于报名人数不一定是5的整数倍,所以最后一组不一定有5个人。考虑到实现的代码片段如下:

......
// 这是发令枪
CountDownLatch startingGun = null;
......
// signupPlayers 是报名队列
// runwayCount 是跑道数量
for (int index = 0 ; index < this.signupPlayers.size() ; index++) {/** 这是发令枪,发令枪的使用规则是:* 1、最多5位选手听从一把发令枪的命令(因为跑道最多就是5条)* 2、如果剩余的没有比赛的选手不足5人,则这些人听从一把发令枪的命令* */if(index % runwayCount == 0) {startingGun =  this.signupPlayers.size() - index > runwayCount?new CountDownLatch(runwayCount):new CountDownLatch(this.signupPlayers.size() - index);}// 获取这个选手(signupPlayers是报名队列)Player player = this.signupPlayers.get(index);// 设置选手关注的发令枪player.setStartingGun(startingGun);// 提交给裁判组协调执行Future<Result> future = refereeService.submit(player);// 开始一个选手的跑步动作状态监控new FutureThread(future, player, this.preliminaries).start();
}
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

这样我们就完成了对第一次“100米赛跑”实现代码的优化:增加了选手分组控制功能。以下给出相对完整的代码。注意,由于Result类没有做任何更改,所以就不需要赘述了。

  • Player选手类的的更改,主要是加入了“关注的发令枪”的关注
public class Player implements Callable<Result> , Comparable<Player>{....../*** 跑道*/private Semaphore runway;/*** 选手所关注的发令枪*/private CountDownLatch startingGun;....../*** @param startingGun the startingGun to set*/public void setStartingGun(CountDownLatch startingGun) {this.startingGun = startingGun;}/*** @return the startingGun*/public CountDownLatch getStartingGun() {return startingGun;}....../* (non-Javadoc)* @see java.util.concurrent.Callable#call()*/@Overridepublic Result call() throws Exception {this.result = null;try {// 申请上跑道this.runway.acquire();// 等待可能的发令枪if(this.startingGun != null) {// 执行到这里,说明这个选手已经拿到了跑到资源;// 首先向发令枪表达“我已准备好”,即计数器-1this.startingGun.countDown();System.out.println("选手" + name + "[" + number + "],已登上跑道,等待发令枪!");// 接下来进入“等待”状态// 以便这个发令枪所管理的所有选手登上跑道了,再一起跑步this.startingGun.await();System.out.println("选手" + name + "[" + number + "],跑!");}// 开始正式跑步return this.result = this.doRun();} catch(Exception e) {e.printStackTrace(System.out);} finally {// 都要进入初赛结果排序(中途退赛的成绩就为0)this.runway.release();System.out.println("选手" + name + "[" + number + "],比赛正常完成!");}// 如果执行到这里,说明异常发生了this.result = new Result(Float.MAX_VALUE);return this.result;}/*** 开始跑步(跑步的处理过程没有变化)* @return* @throws Exception*/private Result doRun()  throws Exception {/** 为了表现一个选手每一次跑步都有不同的状态(但是都不会低于其最低状态),* 所以每一次跑步,系统都会为这个选手分配一个即时速度。* * 这个即时速度不会低于其最小速度,但是也不会高于 14米/秒(否则就是‘超人’咯)* */// 生成即时速度float presentSpeed = 0f;presentSpeed = this.minSpeed * (1.0f + new Random().nextFloat());if(presentSpeed > 14f) {presentSpeed = 14f;}// 计算跑步结果(BigDecimal的使用可自行查阅资料)BigDecimal calculation =  new BigDecimal(100).divide(new BigDecimal(presentSpeed) , 3, RoundingMode.HALF_UP);float presentTime = calculation.floatValue();// 让线程等待presentSpeed的时间,模拟该选手跑步的过程synchronized (this) {this.wait((long)(presentTime * 1000f));}// 返回跑步结果this.result = new Result(presentTime);return result;}......
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • TwoTrack比赛主控制类,主要加入了对“选手分组”的支持
/*** 这是第二个比赛程序。* @author yinwenjie**/
public class TwoTrack {......public void track() {/** 赛跑分为以下几个阶段进行;* * 1、报名* 2、初赛,11名选手,分成两组,每组最多5名选手。* 因为场地只有5条赛道,只有拿到进场许可的才能使用赛道,进行比赛* * 3、决赛:初赛结果将被写入到一个队列中进行排序,只有成绩最好的前五名选手,可以参加决赛。* * 4、决赛结果的前三名将分别作为冠亚季军被公布出来* *///1、================报名// 这就是跑道,需求上说了只有5条跑道,所以只有5个permits。Semaphore runway = new Semaphore(5);this.signupPlayers.clear();for(int index = 0 ; index < TwoTrack.PLAYERNAMES.length ; ) {Player player = new Player(TwoTrack.PLAYERNAMES[index], ++index , runway);this.signupPlayers.add(player);}//2、================进行初赛// 这是赛道的数量int runwayCount = 5;// 这是裁判ExecutorService refereeService = Executors.newFixedThreadPool(5);// 这是发令枪CountDownLatch startingGun = null;for (int index = 0 ; index < this.signupPlayers.size() ; index++) {/** 这是发令枪,发令枪的使用规则是:* 1、最多5位选手听从一把发令枪的命令(因为跑道最多就是5条)* 2、如果剩余的没有比赛的选手不足5人,则这些人听从一把发令枪的命令* */if(index % runwayCount == 0) {startingGun =  this.signupPlayers.size() - index > runwayCount?new CountDownLatch(runwayCount):new CountDownLatch(this.signupPlayers.size() - index);}// 获取这个选手Player player = this.signupPlayers.get(index);// 设置选手关注的发令枪player.setStartingGun(startingGun);// 提交给裁判组准备执行Future<Result> future = refereeService.submit(player);// 开始一个选手的跑步动作状态监控new FutureThread(future, player, this.preliminaries).start();}//! 只有当PLAYERNAMES.length位选手的成绩都产生了,才能进入决赛,这很重要while(this.preliminaries.size() < TwoTrack.PLAYERNAMES.length) {try {synchronized (this.preliminaries) {this.preliminaries.wait();}} catch(InterruptedException e) {e.printStackTrace(System.out);}}// 3、============决赛(只有初赛结果的前5名可以参见)// 决赛的发令枪startingGun = new CountDownLatch(5);for(int index = 0 ; index < 5 ; index++) {Player player = this.preliminaries.poll();// 重新设置选手关注的发令枪player.setStartingGun(startingGun);// 提交给裁判组准备执行Future<Result> future = refereeService.submit(player);// 开始一个选手的跑步动作状态监控new FutureThread(future, player, this.finals).start();}//! 只有当5位选手的决赛成绩都产生了,才能到下一步:公布成绩while(this.finals.size() < 5) {try {synchronized (this.finals) {this.finals.wait();}} catch(InterruptedException e) {e.printStackTrace(System.out);}}// 4、============公布决赛成绩(前三名)for(int index = 0 ; index < 3 ; index++) {Player player = this.finals.poll();switch (index) {case 0:System.out.println("第一名:"  + player.getName() + "[" + player.getNumber() + "],成绩:" + player.getResult().getTime() + "秒");break;case 1:System.out.println("第二名:"  + player.getName() + "[" + player.getNumber() + "],成绩:" + player.getResult().getTime() + "秒");break;case 2:System.out.println("第三名:"  + player.getName() + "[" + player.getNumber() + "],成绩:" + player.getResult().getTime() + "秒");break;default:break;}}}......//其他诸如FutureThread、main函数的代码都没有变化
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115

3-4-3、相似工具类CyclicBarrier

在JDK1.5+中还有一个和CountDownLatch类似的同步计数工具:CyclicBarrier。不同的是CyclicBarrier的计数是循环进行的,而且也不需要向CountDownLatch那样显示的调用countDown进行减一操作。

如何理解CyclicBarrier计数器的循环工作方式呢?我们先来看看一个比较简单的示例代码:

public class TestCyclicBarrier {static {BasicConfigurator.configure();}/*** 日志*/private static Log LOGGER = LogFactory.getLog(TestCyclicBarrier.class);public static void main(String[] args) throws Throwable {// 同步计数器的技术周期为3final CyclicBarrier cyclicBarrier = new CyclicBarrier(3);// 启动子线程,处理“其他”业务for(int index = 0 ; index < 5 ; index++) {Thread childThread = new Thread() {@Overridepublic void run() {// 可获得设置的屏障数值// int parties = cyclicBarrier.getParties();// 可获取当前已经进入等待状态的任务数量// int numberWaiting = cyclicBarrier.getNumberWaiting();TestCyclicBarrier.LOGGER.info("本线程已准备好处理业务......");try {cyclicBarrier.await();} catch (InterruptedException | BrokenBarrierException e) {TestCyclicBarrier.LOGGER.error(e.getMessage() , e);} TestCyclicBarrier.LOGGER.info("开始处理业务......");}};childThread.start();}}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

我们可以用下图表示以上代码的工作过程:

这里写图片描述

在上图中,CyclicBarrier的parties屏障设置为3,其意义是只要有通过CyclicBarrier的await方法进入阻塞等待的线程数量达到了3,则CyclicBarrier就解除这些线程的阻塞状态让他们可以继续执行。所以可以理解为CyclicBarrier的计数功能是可重复使用的,当等待的线程数量达到了设置的屏障值就放行这些线程

3-4-4、使用CyclicBarrier改写“比赛”

下面我们将“比赛”中使用CountDownLatch实现的发令枪改写成使用CyclicBarrier来实现。改写发令枪不会使发令枪的工作职责发生任何变化,所以改写量是比较小的。另外由于这个小节中我们已经给出了很多代码了,为了节约篇幅这里只给出最小化的代码片段。

  • Player选手类中,关于发令枪的定义要修改:
......
/*** 选手所关注的发令枪*/
private CyclicBarrier startingGun;/*** @param startingGun the startingGun to set*/
public void setStartingGun(CyclicBarrier startingGun) {this.startingGun = startingGun;
}/*** @return the startingGun*/
public CyclicBarrier getStartingGun() {return startingGun;
}
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • Player选手类中的发令枪使用部分需要改写。使用CyclicBarrier后就不需要显示调用countDown()方法了:
......
// 申请上跑道
this.runway.acquire();
// 等待可能的发令枪
if(this.startingGun != null) {// 执行到这里,说明这个选手已经拿到了跑到资源;System.out.println("选手" + name + "[" + number + "],已登上跑道,等待发令枪!");// 接下来进入“等待”状态// 以便这个发令枪所管理的所有选手登上跑道了,再一起跑步this.startingGun.await();System.out.println("选手" + name + "[" + number + "],跑!");
}// 开始正式跑步
return this.result = this.doRun();
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • TwoTrack主操作类中,关于发令枪的定义要进行变更:从CountDownLatch变成CyclicBarrier:
......
// 这是发令枪
CyclicBarrier startingGun = null;
......
  • 1
  • 2
  • 3
  • 4
  • TwoTrack主操作类中,根据条件决定CyclicBarrier中parties屏障值的代码业务要进行调整。从之前确定CountDownLatch计数初值变化而来:
......
if(index % runwayCount == 0) {startingGun =  this.signupPlayers.size() - index > runwayCount?new CyclicBarrier(runwayCount):new CyclicBarrier(this.signupPlayers.size() - index);
}
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里我们就不再赘述代码的工作效果了,因为工作效果不会有任何变化。

来源:http://blog.csdn.net/yinwenjie(未经允许严禁用于商业用途!)

这篇关于Java多线程之CountDownLatch同步器的使用(六)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B