Java中的異步與線程池解讀
初始化線程的4種方式
1.繼承Thread
Thread01 thread01 = new Thread01();
thread01.start();
public static class Thread01 extends Thread{
@Override
public void run() {
System.out.println("當(dāng)前線程:"+Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("運行結(jié)果:"+i);
}
}2.實現(xiàn)Runnable 接口
Runnable01 runnable01 = new Runnable01();
new Thread(runnable01).start();
public static class Runnable01 implements Runnable{
@Override
public void run() {
System.out.println("當(dāng)前線程:"+Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("運行結(jié)果:"+i);
}
}3.實現(xiàn)Callable 接口+ FutureTask (可以拿到返回結(jié)果,可以處理異常)
Callabel01 callabel01 = new Callabel01();
FutureTask<Integer> integerFutureTask = new FutureTask<>(callabel01);
//阻塞等待整個線程執(zhí)行完成,獲取返回結(jié)果
Integer integer = integerFutureTask.get();
new Thread(integerFutureTask).start();
public static class Callabel01 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("當(dāng)前線程:"+Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("運行結(jié)果:"+i);
return i;
}
}在業(yè)務(wù)代碼里面不建議使用以上三種啟動線程的方式
4.線程池
應(yīng)該將所有的多線程異步任務(wù)都交給線程池執(zhí)行,進行有效的資源控制
//當(dāng)前系統(tǒng)中池只有一兩個,每一個異步任務(wù)直接提交給線程池,讓他自己去執(zhí)行 ExecutorService service = Executors.newFixedThreadPool(10); //執(zhí)行 service.execute(new Runnable01());
區(qū)別
1/2兩種方式都不能獲取返回值
1/2/3都不能達到資源控制的效果
只有4能控制資源,系統(tǒng)性能是穩(wěn)定的
創(chuàng)建線程池(ExecutorService)
1.Executors 工具類創(chuàng)建
//當(dāng)前系統(tǒng)中池只有一兩個,每一個異步任務(wù)直接提交給線程池,讓他自己去執(zhí)行 ExecutorService service = Executors.newFixedThreadPool(10); //執(zhí)行 service.execute(new Runnable01());
2.原生方法創(chuàng)建線程池
ThreadPoolExecutor需要傳入七大參數(shù)
corePoolSize核心線程數(shù)【一直存在,除非設(shè)置了允許線程超時的設(shè)置:allowCoreThreadTimeOut】,保留在池中的線??程數(shù),線程池創(chuàng)建后好后就準備就緒的線程數(shù),就等待異步任務(wù)去執(zhí)行,new 好了 Thread,等待異步任務(wù)maximumPoolSize池中最大線程數(shù)量,控制資源并發(fā)keepAliveTime存活時間,當(dāng)前正在運行的線程數(shù)量,大于核心線程數(shù),就會釋放空閑的線程,只要線程空閑大于指定存活時間,釋放的線程是指最大的線程數(shù)量減去核心線程數(shù),unit時間單位BlockingQueue workQueue阻塞隊列,如果任務(wù)有很多,就會將目前多的隊伍放在隊列里面,只要有空閑的線程,就會去隊列里面取出新的任務(wù)繼續(xù)執(zhí)行。new LinkedBlockingQueue<>()默認值是Integer的最大值,會導(dǎo)致內(nèi)存不夠,一定要傳入業(yè)務(wù)定制的大小,可以通過壓測得出峰值threadFactory線程的創(chuàng)建工廠handler如果隊列滿了,按照我們指定的拒絕策略拒絕執(zhí)行任務(wù)
3.線程池的運行流程 線程池創(chuàng)建
準備好core 數(shù)量的核心線程,準備接受任務(wù)新的任務(wù)進來,用core 準備好的空閑線程執(zhí)行。
(1) 、core 滿了,就將再進來的任務(wù)放入阻塞隊列中??臻e的core 就會自己去阻塞隊列獲取任務(wù)執(zhí)行
(2) 、阻塞隊列滿了,就直接開新線程執(zhí)行,最大只能開到max 指定的數(shù)量
(3) 、max 都執(zhí)行好了。Max-core 數(shù)量空閑的線程會在keepAliveTime 指定的時間后自動銷毀。最終保持到core 大小
(4) 、如果線程數(shù)開到了max 的數(shù)量,還有新任務(wù)進來,就會使用reject 指定的拒絕策略進行處理所有的線程創(chuàng)建都是由指定的factory 創(chuàng)建的。
一個線程池core 7; max 20 ,queue:50,100 并發(fā)進來怎么分配的;先有7 個能直接得到執(zhí)行,接下來50 個進入隊列排隊,在多開13 個繼續(xù)執(zhí)行。
現(xiàn)在70 個被安排上了。剩下30 個默認拒絕策略。拒絕策略一般是拋棄,如果不想拋棄還要執(zhí)行,可以使用同步的方式執(zhí)行,或者丟棄最老的

4. 四種常見的線程池
newCachedThreadPool創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。核心線程固定是0,所有都可回收newFixedThreadPool創(chuàng)建一個固定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待。固定大小,核心 = 最大newScheduledThreadPool創(chuàng)建一個固定長線程池,支持定時及周期性任務(wù)執(zhí)行。定時任務(wù)線程池newSingleThreadExecutor創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。后臺從隊列里面獲取任務(wù) 挨個執(zhí)行
為什么要使用線程池
降低資源的消耗
通過重復(fù)利用已經(jīng)創(chuàng)建好的線程降低線程的創(chuàng)建和銷毀帶來的損耗
提高響應(yīng)速度
因為線程池中的線程數(shù)沒有超過線程池的最大上限時,有的線程處于等待分配任務(wù)的狀態(tài),當(dāng)任務(wù)來時無需創(chuàng)建新的線程就能執(zhí)行
提高線程的可管理性
線程池會根據(jù)當(dāng)前系統(tǒng)特點對池內(nèi)的線程進行優(yōu)化處理,減少創(chuàng)建和銷毀線程帶來的系統(tǒng)開銷。無限的創(chuàng)建和銷毀線程不僅消耗系統(tǒng)資源,還降低系統(tǒng)的穩(wěn)定性,使用線程池進行統(tǒng)一分配
CompletableFuture 異步編排
業(yè)務(wù)場景:
查詢商品詳情頁的邏輯比較復(fù)雜,有些數(shù)據(jù)還需要遠程調(diào)用,必然需要花費更多的時間

假如商品詳情頁的每個查詢,需要如下標(biāo)注的時間才能完成那么,用戶需要5.5s 后才能看到商品詳情頁的內(nèi)容。很顯然是不能接受的。
如果有多個線程同時完成這6 步操作,也許只需要1.5s 即可完成響應(yīng)。
CompletableFuture 和FutureTask 同屬于Future 接口的實現(xiàn)類,都可以獲取線程的執(zhí)行結(jié)果。

1.創(chuàng)建異步對象

1、runXxxx 都是沒有返回結(jié)果的,supplyXxx 都是可以獲取返回結(jié)果的
2、可以傳入自定義的線程池,否則就用默認的線程池;
沒有返回結(jié)果的
static ExecutorService service = Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(()->{
System.out.println("當(dāng)前線程:"+Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("運行結(jié)果:"+i);
},service);有返回結(jié)果的
static ExecutorService service = Executors.newFixedThreadPool(10);
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("當(dāng)前線程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("運行結(jié)果:" + i);
return i;
}, service);
Integer integer = future.get();
System.out.println("main----end"+integer);2.計算完成時(線程執(zhí)行成功)回調(diào)方法

whenComplete 可以處理正常和異常的計算結(jié)果,雖然可以得到異常信息,但是不能修改返回數(shù)據(jù)exceptionally 處理異常情況。
可以感知異常并返回默認值whenComplete 和whenCompleteAsync 的區(qū)別:
whenComplete:是執(zhí)行當(dāng)前任務(wù)的線程執(zhí)行繼續(xù)執(zhí)行whenComplete 的任務(wù)。whenCompleteAsync:是執(zhí)行把whenCompleteAsync 這個任務(wù)繼續(xù)提交給線程池來進行執(zhí)行。
方法不以Async 結(jié)尾,意味著Action 使用相同的線程執(zhí)行,而Async 可能會使用其他線程執(zhí)行(如果是使用相同的線程池,也可能會被同一個線程選中執(zhí)行)
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main----start");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("當(dāng)前線程:" + Thread.currentThread().getId());
int i = 10 / 0;
System.out.println("運行結(jié)果:" + i);
return i;
}, service).whenComplete((res,excption)->{
System.out.println("異步任務(wù)成功完成:結(jié)果是::::"+res+"異常是:"+excption);
}).exceptionally(throwable->{
//可以感知異常,同時返回數(shù)據(jù)
return 10;
});
Integer integer = future.get();
System.out.println("main----end"+integer);
}3.handle 方法(可對結(jié)果做最后的處理(可處理異常),可改變返回值)

和complete 一樣,可對結(jié)果做最后的處理(可處理異常),可改變返回值。
/* 方法完成后的處理*/
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("當(dāng)前線程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("運行結(jié)果:" + i);
return i;
}, service).handle((res,exption)->{
if (res != null){
return res*2;
}
if (exption != null){
return 0;
}
return 0;
});4.線程串行化

thenApply方法:當(dāng)一個線程依賴另一個線程時,獲取上一個任務(wù)返回的結(jié)果,并返回當(dāng)前任務(wù)的返回值。thenAccept方法:能接收上一步的返回結(jié)果,但是不能改變返回值。thenRun方法:只要上面的任務(wù)執(zhí)行完成,就開始執(zhí)行thenRun,不能改變返回值帶有Async 默認是異步執(zhí)行的。同之前。
以上都要前置任務(wù)成功完成。
Function<? super T,? extends U>
T:上一個任務(wù)返回結(jié)果的類型U:當(dāng)前任務(wù)的返回值類型thenRun方法:只要上面的任務(wù)執(zhí)行完成,就開始執(zhí)行thenRun,不能改變返回值
static ExecutorService service = Executors.newFixedThreadPool(10);
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
System.out.println("當(dāng)前線程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("運行結(jié)果:" + i);
return i;
}, service).thenRunAsync(() -> {
System.out.println("任務(wù)2啟動了");
}, service);thenAccept方法:能接收上一步的返回結(jié)果,但是不能改變返回值。
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
System.out.println("當(dāng)前線程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("運行結(jié)果:" + i);
return i;
}, service).thenAccept((res)->{
System.out.println("異步啟動了:"+res);
});thenApplyAsync技能接收上一步的結(jié)果,又能改變返回值
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("當(dāng)前線程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("運行結(jié)果:" + i);
return i;
}, service);
future.thenApplyAsync((res) -> {
System.out.println("任務(wù)2啟動了:" + res);
return res + "hello";
}, service);
System.out.println("main----end"+future.get());5.兩任務(wù)組合- 都要完成


兩個任務(wù)必須都完成,觸發(fā)該任務(wù)。
thenCombine:組合兩個future,獲取兩個future 的返回結(jié)果,并返回當(dāng)前任務(wù)的返回值thenAcceptBoth:組合兩個future,獲取兩個future 任務(wù)的返回結(jié)果,然后處理任務(wù),沒有返回值。
CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務(wù)1線程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("任務(wù)1運行結(jié)果:" + i);
return i;
}, service);
CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務(wù)2線程:" + Thread.currentThread().getId());
System.out.println("任務(wù)2運行結(jié)果:");
return "hello";
}, service);
future01.thenAcceptBothAsync(future02, (f1, f2) -> {
System.out.println("任務(wù)3開始之前的結(jié)果---f1=" + f1 + "f2=" + f2);
}, service);runAfterBoth:組合兩個future,不獲取前兩個的結(jié)果,只需兩個future 處理完任務(wù)后,處理該任務(wù)。
CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務(wù)1線程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("任務(wù)1運行結(jié)果:" + i);
return i;
}, service);
CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務(wù)2線程:" + Thread.currentThread().getId());
System.out.println("任務(wù)2運行結(jié)果:");
return "hello";
}, service);
future01.runAfterBothAsync(future02,()->{
System.out.println("任務(wù)3開始");
},service);6.兩任務(wù)組合- 一個完成


當(dāng)兩個任務(wù)中,任意一個future 任務(wù)完成的時候,執(zhí)行任務(wù)。
applyToEither:兩個任務(wù)有一個執(zhí)行完成,獲取它的返回值,處理任務(wù)并自己有新的返回值。
CompletableFuture<Object> future01 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務(wù)1線程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("任務(wù)1運行結(jié)果:" + i);
return i;
}, service);
CompletableFuture<Object> future02 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務(wù)2線程:" + Thread.currentThread().getId());
System.out.println("任務(wù)2運行結(jié)果:");
return "hello";
}, service);
future01.applyToEitherAsync(future02,(t) -> {
System.out.println("任務(wù)3開始"+t);
return t.toString() + "niubi";
}, service);acceptEither:兩個任務(wù)有一個執(zhí)行完成,獲取它的返回值,處理任務(wù),自己沒有新的返回值。
CompletableFuture<Object> future01 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務(wù)1線程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("任務(wù)1運行結(jié)果:" + i);
return i;
}, service);
CompletableFuture<Object> future02 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務(wù)2線程:" + Thread.currentThread().getId());
System.out.println("任務(wù)2運行結(jié)果:");
return "hello";
}, service);
future01.acceptEitherAsync(future02,(t) -> {
System.out.println("任務(wù)3開始"+t);
}, service);runAfterEither:兩個任務(wù)有一個執(zhí)行完成,不獲取future 的結(jié)果,處理任務(wù),自己也沒有返回值。
CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務(wù)1線程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("任務(wù)1運行結(jié)果:" + i);
return i;
}, service);
CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
System.out.println("任務(wù)2線程:" + Thread.currentThread().getId());
System.out.println("任務(wù)2運行結(jié)果:");
return "hello";
}, service);
future01.runAfterEitherAsync(future02,() -> {
System.out.println("任務(wù)3開始");
}, service);7.多任務(wù)組合

allOf:等待所有任務(wù)完成
CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
System.out.println("查詢商品的圖片信息");
return "hello.png";
}, service);
CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {
System.out.println("查詢商品的屬性");
return "黑色+256g";
}, service);
CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
System.out.println("查詢商品的介紹");
return "華為";
}, service);
CompletableFuture<Void> completableFuture = CompletableFuture.allOf(futureImg, futureAttr, futureDesc);
completableFuture.get(); //等待所有結(jié)果完成anyOf:只要有一個任務(wù)完成
整合SpringBoot
1.添加配置類,新建線程池
package cn.cloud.xmall.product.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Description: ···
* @author: Freedom
* @QQ: 1556507698
* @date:2022/3/21 17:41
*/
@Configuration
public class MyThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(){
return new ThreadPoolExecutor(
20,
200,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
};
}2.想要在配置文件中手動的配置參數(shù)
新建一個配置屬性類
package cn.cloud.xmall.product.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* @Description: ···
* @author: Freedom
* @QQ: 1556507698
* @date:2022/3/21 17:47
*/
@ConfigurationProperties(prefix = "xmall.thread")
@Component //加入容器
@Data
public class ThreadPollConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}注:可以在依賴種添加此依賴,在配置文件中就會有我們自己配置屬性的提示
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>3.配置文件配置屬性
#線程池配置
xmall:
thread:
core-size: 20
max-size: 200
keep-alive-time: 104.使用配置文件中的屬性
@EnableConfigurationProperties(ThreadPollConfigProperties.class),如果配置文件類沒有添加@Component加入容器可以使用這種方式
package cn.cloud.xmall.product.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Description: ···
* @author: Freedom
* @QQ: 1556507698
* @date:2022/3/21 17:41
*/
//@EnableConfigurationProperties(ThreadPollConfigProperties.class)
@Configuration
public class MyThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPollConfigProperties pool){
return new ThreadPoolExecutor(
pool.getCoreSize(),
pool.getMaxSize(),
pool.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
};
}5.注入線程池
@Autowired
private ThreadPoolExecutor executor;6.異步編排
@Override
public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException {
SkuItemVo skuItemVo = new SkuItemVo();
//1.使用自己的線程池來新建異步任務(wù)
CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
//1.查詢基本信息 pms_sku_info
SkuInfoEntity info = getById(skuId);
skuItemVo.setInfo(info);
return info;
}, executor);
//2.根據(jù)一號任務(wù)來繼續(xù)調(diào)用
CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((res) -> {
//3.獲取當(dāng)前spu的銷售屬性組合
List<SkuItemSaleAttrVo> saleAttrVos = saleAttrValueService.getSaleAttrsBySpuId(res.getSpuId());
skuItemVo.setSaleAttr(saleAttrVos);
}, executor);
//3.根據(jù)一號任務(wù)來繼續(xù)調(diào)用
CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync((res) -> {
//4.獲取Spu的介紹 pms_spu_info_desc
SpuInfoDescEntity spuInfo = spuInfoDescService.getById(res.getSpuId());
skuItemVo.setDesc(spuInfo);
}, executor);
//4.根據(jù)一號任務(wù)來繼續(xù)調(diào)用
CompletableFuture<Void> baseAttrFuture = infoFuture.thenAcceptAsync((res) -> {
//5.獲取spu的規(guī)格參數(shù)信息
List<SpuItemAttrGroupVo> attrGroups = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());
skuItemVo.setGroupAttrs(attrGroups);
}, executor);
//此任務(wù)不需要根據(jù)一號任務(wù)的返回調(diào)用,所以開一個新線程
CompletableFuture<Void> imagesFuture = CompletableFuture.runAsync(() -> {
//2.獲取sku的圖片信息 pms_sku_images
List<SkuImagesEntity> images = imagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(images);
}, executor);
//等待所有任務(wù)都完成
//TODO 可以選擇有異常情況下的處理結(jié)果
CompletableFuture.allOf(saleAttrFuture,descFuture,baseAttrFuture,imagesFuture).get();
return skuItemVo;
}以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中的HashSet詳解和使用示例_動力節(jié)點Java學(xué)院整理
HashSet 是一個沒有重復(fù)元素的集合。接下來通過實例代碼給大家介紹java中的hashset相關(guān)知識,感興趣的朋友一起看看吧2017-05-05
java數(shù)據(jù)結(jié)構(gòu)ArrayList詳解
本文詳細講解了java數(shù)據(jù)結(jié)構(gòu)ArrayList的用法,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-12-12
SpringBoot后端進行數(shù)據(jù)校驗JSR303的使用詳解
這篇文章主要介紹了SpringBoot后端進行數(shù)據(jù)校驗JSR303的使用詳解,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
Java面試題沖刺第六天--網(wǎng)絡(luò)編程1
這篇文章主要為大家分享了最有價值的三道網(wǎng)絡(luò)編程面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,感興趣的小伙伴們可以參考一下2021-07-07
Java使用selenium爬取b站動態(tài)的實現(xiàn)方式
本文主要介紹了Java使用selenium爬取b站動態(tài)的實現(xiàn)方式,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
Spring的兩種事務(wù)管理機制的基本概念和demo示例
Spring事務(wù)包括聲明式事務(wù)管理和注解式事務(wù)管理,我們通過概念和小demo的形式一步一步地來一起學(xué)習(xí)這個知識點,需要的朋友可以參考下2023-07-07
JAVA中ListIterator和Iterator詳解與辨析(推薦)
這篇文章主要介紹了JAVA中ListIterator和Iterator詳解與辨析,需要的朋友可以參考下2017-04-04

