java不同線程解讀以及線程池的使用方式
java不同線程解讀以及線程池的使用
線程池子類比一個(gè)水池,而每一個(gè)線程,都好比水池的水。因此,水池多大,看系統(tǒng)硬件配置。線程池主要為了并發(fā),高效處理任務(wù)。而異步處理任務(wù),可以有效提高處理任務(wù)的吞吐量。
線程池的常見應(yīng)用場(chǎng)景
處理大量而短小的請(qǐng)求,請(qǐng)求數(shù)量很大,每一個(gè)請(qǐng)求開啟一個(gè)線程,在請(qǐng)求完畢之后再對(duì)線程進(jìn)行銷毀,這樣創(chuàng)建和銷毀線程所消耗的時(shí)間往往比任務(wù)本身所需消耗的資源要大得多。
線程池做到線程復(fù)用,不需要頻繁的創(chuàng)建和銷毀線程,線程池中的線程一直存在于線程池中,線程從任務(wù)隊(duì)列中取得任務(wù)來執(zhí)行。而且這樣做的另一個(gè)好處有,通過適當(dāng)?shù)卣{(diào)整線程池中的線程數(shù)目,也就是當(dāng)請(qǐng)求的數(shù)目超過某個(gè)閾值時(shí),就強(qiáng)制其它任何新到的請(qǐng)求一直等待,直到獲得一個(gè)線程來處理為止,從而可以防止資源不足。
線程池是什么?
線程池用于多線程處理中,它可以根據(jù)系統(tǒng)的情況,可以控制線程執(zhí)行的數(shù)量,優(yōu)化運(yùn)行效果。線程池做的工作主要是控制運(yùn)行的線程的數(shù)量,處理過程中將任務(wù)放入隊(duì)列,然后在線程創(chuàng)建后啟動(dòng)這些任務(wù),如果線程數(shù)量超過了最大數(shù)量超出數(shù)量的線程排隊(duì)等候,等其它線程執(zhí)行完畢,再?gòu)年?duì)列中取出任務(wù)來執(zhí)行。
線程池的作用
創(chuàng)建對(duì)象和銷毀對(duì)象是非常消耗時(shí)間和資源的。因此想要最小化這種消耗的一種思想就是『池化資源』,通過重用線程池中的資源來減少創(chuàng)建和銷毀線程所需要耗費(fèi)的時(shí)間和資源。
線程池的一個(gè)作用是創(chuàng)建和銷毀線程的次數(shù),每個(gè)工作線程可以多次使用;另一個(gè)作用是可根據(jù)系統(tǒng)情況調(diào)整執(zhí)行的線程數(shù)量,防止消耗過多內(nèi)存。另外,通過線程池,能有效的控制線程的最大并發(fā)數(shù),提高系統(tǒng)資源利用率,同時(shí)避免過多的資源競(jìng)爭(zhēng),避免堵塞。
線程池的優(yōu)點(diǎn)總結(jié)如下幾個(gè)方面:
- 線程復(fù)用
- 控制最大并發(fā)數(shù)
- 管理線程
線程池組成:
- 線程池管理器:用于創(chuàng)建并管理線程池
- 工作線程:線程池中的線程
- 任務(wù)接口:每個(gè)任務(wù)必須實(shí)現(xiàn)的接口,用于工作線程調(diào)度其運(yùn)行
- 任務(wù)隊(duì)列:用于存放待處理的任務(wù),提供一種緩沖機(jī)制
1、Async 和 @Async 很關(guān)鍵
兩個(gè)注解很關(guān)鍵:
- @EnableAsync 啟用基于異步方法的執(zhí)行
- @Async 這注解的函數(shù)會(huì)被異步處理
2、Thread和Runnable
Thread可以創(chuàng)建線程,但是線程使用完之后需要對(duì)線程資源進(jìn)行銷毀回收,消耗資源,且容易造成線程上下文切換(操作系統(tǒng)核心對(duì)CPU上對(duì)進(jìn)程或者線程進(jìn)行切換)問題,線程管理不當(dāng)容易資源耗盡,不建議使用。、
一個(gè)類實(shí)現(xiàn) Runnable 接口可以將該類的實(shí)例傳遞給一個(gè) Thread 對(duì)象并啟動(dòng)一個(gè)新線程,這個(gè)新線程就會(huì)執(zhí)行該類中 void run() 方法中所定義的邏輯。
3、數(shù)據(jù)類型—線程安全
如果有多線程共享數(shù)據(jù)的方式,要牢記各種使用場(chǎng)景的線程安全數(shù)據(jù)類型。
- (1)、AtomicInteger 原子int整型;
- (2)、AtomicLong 原子long整型;
- (3)、AtomicBoolean 原子boolean;
- (4)、List 這三種都是線程安全型:
①List<T> vector = new Vector<>(); ②<T> listSyn = Collections.synchronizedList(new ArrayList<>()); ③List<T> copyList = new CopyOnWriteArrayList<>();
4、@Configuration配置類和 @Bean將實(shí)例對(duì)象交給IOC容器
5、ThreadPoolExecutor 和 ThreadPoolTaskExecutor
兩種線程池方式,本質(zhì)上一樣,ThreadPoolTaskExecutor(我使用的) 源碼上是在 ThreadPoolExecutor 上再加了一層包裝,為了更方便在spring框架中使用。
- 配置類: 可以確保異步執(zhí)行配置對(duì)應(yīng)用中的所有 bean 都生效。
- 啟動(dòng)類: 啟動(dòng)類上整個(gè)應(yīng)用中啟用異步
- 推薦: 針對(duì)應(yīng)用中的不同部分提供不同的異步執(zhí)行策略,或者只需要特定的一部分 bean 具備異步執(zhí)行能力,放配置類上。如果整個(gè)應(yīng)用都需要異步支持,放置在啟動(dòng)類。
使用@Bean(“beanName”)定義線程池 然后在@Async(“beanName”)中引用指定的線程池
@EnableAsync
用于啟用整個(gè)應(yīng)用程序的異步處理功能,包括所有通過 @Async
注解標(biāo)記的方法。它不負(fù)責(zé)配置底層線程池。
ThreadPoolTaskExecutor
Bean 配置則是用于配置具體的線程池實(shí)例,這個(gè)線程池會(huì)被 @Async
方法所使用。
如果你在配置類中同時(shí)使用了 @EnableAsync
注解和自定義的 ThreadPoolTaskExecutor
Bean 配置,Spring 將會(huì)使用你配置的線程池執(zhí)行異步方法。如果你只使用 @EnableAsync
,Spring 將會(huì)使用默認(rèn)的線程池配置。
注意:
- 1、除了要在方法上加@Async注解,還需要在啟動(dòng)類加注解@EnableAsync啟動(dòng)多線程注解,@Async就會(huì)對(duì)標(biāo)注的方法開啟異步多線程調(diào)用,注意,這個(gè)方法的類一定要交給Spring容器來管理。
- 2、法一定要從另一個(gè)類中調(diào)用,也就是從類的外部調(diào)用,類的內(nèi)部調(diào)用是無效的,因?yàn)锧Transactional和@Async注解的實(shí)現(xiàn)都是基于Spring的AOP,而AOP的實(shí)現(xiàn)是基于動(dòng)態(tài)代理模式實(shí)現(xiàn)的。那么注解失效的原因就很明顯了,有可能因?yàn)檎{(diào)用方法的是對(duì)象本身而不是代理對(duì)象,因?yàn)闆]有經(jīng)過Spring容器
- 3、異步方法使用注解@Async的返回值只能為void或者Future
- 4、方法必須是public方法
定義線程池的配置
package com.test.wll.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.ThreadPoolExecutor; @EnableAsync//或者加在app類上,此處不加需要在啟動(dòng)類上加 @Configuration public class ThreadPoolTaskConfig { @Bean("threadPoolRedisTaskExecutor") public ThreadPoolTaskExecutor threadPoolRedisTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //線程池創(chuàng)建的核心線程數(shù),線程池維護(hù)線程的最少數(shù)量,即使沒有任務(wù)需要執(zhí)行,也會(huì)一直存活 executor.setCorePoolSize(8); //如果設(shè)置allowCoreThreadTimeout=true(默認(rèn)false)時(shí),核心線程會(huì)超時(shí)關(guān)閉 //executor.setAllowCoreThreadTimeOut(true); //阻塞隊(duì)列 當(dāng)核心線程數(shù)達(dá)到最大時(shí),新任務(wù)會(huì)放在隊(duì)列中排隊(duì)等待執(zhí)行 executor.setQueueCapacity(124); //最大線程池?cái)?shù)量,當(dāng)線程數(shù)>=corePoolSize,且任務(wù)隊(duì)列已滿時(shí)。線程池會(huì)創(chuàng)建新線程來處理任 //任務(wù)隊(duì)列已滿時(shí), 且當(dāng)線程數(shù)=maxPoolSize,,線程池會(huì)拒絕處理任務(wù)而拋出異常 executor.setMaxPoolSize(64); //當(dāng)線程空閑時(shí)間達(dá)到keepAliveTime時(shí),線程會(huì)退出,直到線程數(shù)量=corePoolSize //允許線程空閑時(shí)間30秒,當(dāng)maxPoolSize的線程在空閑時(shí)間到達(dá)的時(shí)候銷毀 //如果allowCoreThreadTimeout=true,則會(huì)直到線程數(shù)量=0 executor.setKeepAliveSeconds(30); //spring 提供的 ThreadPoolTaskExecutor 線程池,是有setThreadNamePrefix() 方法的。 //jdk 提供的ThreadPoolExecutor 線程池是沒有 setThreadNamePrefix() 方法的 executor.setThreadNamePrefix("threadPoolRedisTaskExecutor"); // rejection-policy:拒絕策略:當(dāng)線程數(shù)已經(jīng)達(dá)到maxSize的時(shí)候,如何處理新任務(wù) // CallerRunsPolicy():交由調(diào)用方線程運(yùn)行,比如 main 線程;如果添加到線程池失敗,那么主線程會(huì)自己去執(zhí)行該任務(wù),不會(huì)等待線程池中的線程去執(zhí)行 // AbortPolicy():該策略是線程池的默認(rèn)策略,如果線程池隊(duì)列滿了丟掉這個(gè)任務(wù)并且拋出RejectedExecutionException異常。 // DiscardPolicy():如果線程池隊(duì)列滿了,會(huì)直接丟掉這個(gè)任務(wù)并且不會(huì)有任何異常 // DiscardOldestPolicy():丟棄隊(duì)列中最老的任務(wù),隊(duì)列滿了,會(huì)將最早進(jìn)入隊(duì)列的任務(wù)刪掉騰出空間,再嘗試加入隊(duì)列 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); executor.initialize(); return executor; } } } //最大線程數(shù) executor.setMaxPoolSize(maxPoolSize); //核心線程數(shù) executor.setCorePoolSize(corePoolSize); //任務(wù)隊(duì)列的大小 executor.setQueueCapacity(queueCapacity); //線程前綴名 executor.setThreadNamePrefix(namePrefix); //線程存活時(shí)間 executor.setKeepAliveSeconds(keepAliveSeconds);
//此處的名字要與線程池的名字一致,清晰的命名可以清晰的了解哪個(gè)線程報(bào)錯(cuò) @Async("threadName") @GetMapping("/方法名字") public Result<String> test() { }
6、拒絕策略
rejectedExectutionHandler參數(shù)字段用于配置絕策略,常用拒絕策略如下
AbortPolicy
:用于被拒絕任務(wù)的處理程序,它將拋出RejectedExecutionExceptionCallerRunsPolicy
:用于被拒絕任務(wù)的處理程序,直接在execute方法的調(diào)用線程中運(yùn)行被拒絕的任務(wù)。DiscardOldestPolicy
:用于被拒絕任務(wù)的處理程序,放棄最舊的未處理請(qǐng)求,然后重試execute。DiscardPolicy
:用于被拒絕任務(wù)的處理程序,默認(rèn)情況下它將丟棄被拒絕的任務(wù)。
7、線程池處理流程
線程池的工作流程(必知必會(huì))?
這個(gè)問題回答的時(shí)候,最好用講故事的方式進(jìn)行。 假如核心線程數(shù)是5,最大線程數(shù)是10,阻塞隊(duì)列也是10
- 1)有新任務(wù)來的時(shí)候,將先使用核心線程執(zhí)行;
- 2)當(dāng)任務(wù)數(shù)達(dá)到5個(gè)的時(shí)候,第6個(gè)任務(wù)開始排隊(duì);
- 3)當(dāng)任務(wù)數(shù)達(dá)到15個(gè)的時(shí)候,第16個(gè)任務(wù)將開啟新的線程執(zhí)行,也就是第6個(gè)線程
- 4)當(dāng)任務(wù)數(shù)達(dá)到20個(gè)的時(shí)候,線程池滿了,如果有第21個(gè)任務(wù),將執(zhí)行拒絕策略
8、 Hutool 的ThreadUtil.execute
execute
方法是通過 GlobalThreadPool
來執(zhí)行任務(wù)的,而 GlobalThreadPool
在 Hutool 中是一個(gè)全局的線程池。
它會(huì)使用默認(rèn)的配置來初始化線程池,這些默認(rèn)配置在 Hutool 內(nèi)部已經(jīng)設(shè)定好了,因此在使用 execute
方法時(shí)不需要手動(dòng)指定核心池大小和最大池大小。
這種方法適合簡(jiǎn)單的任務(wù)執(zhí)行,如果需要更靈活的線程池配置(比如自定義線程數(shù)、隊(duì)列類型等),可以考慮使用 newExecutor
等方法手動(dòng)創(chuàng)建線程池,并進(jìn)行更詳細(xì)的配置。
spring的 ThreadPoolTaskExecutor
:
- 優(yōu)點(diǎn): Spring 的
ThreadPoolTaskExecutor
是 Spring 框架提供的一個(gè)線程池實(shí)現(xiàn),它提供了很多配置選項(xiàng),允許你更加靈活地定制線程池的行為,比如核心線程數(shù)、最大線程數(shù)、隊(duì)列容量、線程存活時(shí)間等。 - 適用情況: 適合在 Spring 環(huán)境中使用,對(duì)于基于 Spring 的項(xiàng)目,使用這種方式可以充分利用 Spring 提供的特性和管理能力,例如可以方便地集成到 Spring 的任務(wù)調(diào)度中
Hutool ThreadUtil
:
- 優(yōu)點(diǎn): Hutool 提供的
ThreadUtil
類是一個(gè)簡(jiǎn)化了的工具類,提供了一些靜態(tài)方法來方便地創(chuàng)建線程池和執(zhí)行任務(wù)。它是一個(gè)輕量級(jí)的工具庫(kù),適合在一般的 Java 程序中使用,不依賴于 Spring 框架。 - 適用情況: 適合非 Spring 項(xiàng)目或者不需要集成到 Spring 容器管理的場(chǎng)景。如果你不需要太多的線程池配置選項(xiàng),而只是簡(jiǎn)單地執(zhí)行任務(wù),使用
ThreadUtil
的execute
方法會(huì)更加便捷。
常見線程池
- 1)定長(zhǎng)線程池(FixedThreadPool)
- 2)定時(shí)線程池(ScheduledThreadPool)
- 3)可緩存線程池(CachedThreadPool)
- 4)單線程化線程池(SingleThreadExecutor)
核心概念:這四個(gè)線程池的本質(zhì)都是ThreadPoolExecutor對(duì)象(看源碼)
不同點(diǎn)在于:
- 1)FixedThreadPool:只有核心線程,線程數(shù)量固定,執(zhí)行完立即回收,任務(wù)隊(duì)列為鏈表結(jié)構(gòu)的有界隊(duì)列。
- 2)ScheduledThreadPool:核心線程數(shù)量固定,非核心線程數(shù)量無限,執(zhí)行完閑置 10ms 后回收,任務(wù)隊(duì)列為延時(shí)阻塞隊(duì)列。
- 3)CachedThreadPool:無核心線程,非核心線程數(shù)量無限,執(zhí)行完閑置 60s 后回收,任務(wù)隊(duì)列為不存儲(chǔ)元素的阻塞隊(duì)列。
- 4)SingleThreadExecutor:只有 1 個(gè)核心線程,無非核心線程,執(zhí)行完立即回收,任務(wù)隊(duì)列為鏈表結(jié)構(gòu)的有界隊(duì)列
線程池的主要參數(shù)有哪些(必知必會(huì))?
- 1)corePoolSize(必需):核心線程數(shù)。默認(rèn)情況下,核心線程會(huì)一直存活,但是當(dāng)將 allowCoreThreadTimeout 設(shè)置為 true 時(shí),核心線程也會(huì)超時(shí)回收。
- 2)maximumPoolSize(必需):線程池所能容納的最大線程數(shù)。當(dāng)活躍線程數(shù)達(dá)到該數(shù)值后,后續(xù)的新任務(wù)將會(huì)阻塞。
- 3)keepAliveTime(必需):線程閑置超時(shí)時(shí)長(zhǎng)。如果超過該時(shí)長(zhǎng),非核心線程就會(huì)被回收。如果將 allowCoreThreadTimeout 設(shè)置為 true 時(shí),核心線程也會(huì)超時(shí)回收。
- 4)unit(必需):指定 keepAliveTime 參數(shù)的時(shí)間單位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
- 5)workQueue(必需):任務(wù)隊(duì)列。通過線程池的 execute() 方法提交的 Runnable 對(duì)象將存儲(chǔ)在該參數(shù)中。其采用阻塞隊(duì)列實(shí)現(xiàn)。
- 6)threadFactory(可選):線程工廠。用于指定為線程池創(chuàng)建新線程的方式。
- 7)handler(可選):拒絕策略。當(dāng)達(dá)到最大線程數(shù)時(shí)需要執(zhí)行的飽和策略。
線程池的工作流程(必知必會(huì))?
這個(gè)問題回答的時(shí)候,最好用講故事的方式進(jìn)行。 假如核心線程數(shù)是5,最大線程數(shù)是10,阻塞隊(duì)列也是10
- 1)有新任務(wù)來的時(shí)候,將先使用核心線程執(zhí)行;
- 2)當(dāng)任務(wù)數(shù)達(dá)到5個(gè)的時(shí)候,第6個(gè)任務(wù)開始排隊(duì);
- 3)當(dāng)任務(wù)數(shù)達(dá)到15個(gè)的時(shí)候,第16個(gè)任務(wù)將開啟新的線程執(zhí)行,也就是第6個(gè)線程
- 4)當(dāng)任務(wù)數(shù)達(dá)到20個(gè)的時(shí)候,線程池滿了,如果有第21個(gè)任務(wù),將執(zhí)行拒絕策略
線程安全
函數(shù)、函數(shù)庫(kù)在并發(fā)環(huán)境中被調(diào)用時(shí),能夠正確地處理多個(gè)線程之間的共享變量,使程序功能正確完成。
- 并發(fā):(由cpu分配時(shí)間片,看似像同時(shí)干多個(gè)事情實(shí)際是由不同的cpu時(shí)間片干)任務(wù)執(zhí)行時(shí)間比調(diào)度頻率長(zhǎng),會(huì)導(dǎo)致任務(wù)堆積,任務(wù)完成后,才能開始下一個(gè)任務(wù),可能會(huì)造成任務(wù)被阻塞,直到有空閑線程可用。當(dāng)任務(wù)執(zhí)行時(shí)間比調(diào)度頻率長(zhǎng)時(shí),會(huì)出現(xiàn)并發(fā)問題,線程池中的線程會(huì)被任務(wù)堵塞,直到前一個(gè)任務(wù)執(zhí)行完成。
- 并行:當(dāng)系統(tǒng)有一個(gè)以上CPU時(shí),當(dāng)一個(gè)CPU執(zhí)行一個(gè)進(jìn)程時(shí),另一個(gè)CPU可以執(zhí)行另一個(gè)進(jìn)程,兩個(gè)進(jìn)程互不搶占CPU資源,可以同時(shí)進(jìn)行,這種方式我們稱之為并行(Parallel)。
eg:并發(fā)是兩個(gè)隊(duì)伍交替使用一臺(tái)咖啡機(jī)。并行是兩個(gè)隊(duì)伍同時(shí)使用兩臺(tái)咖啡機(jī)。
- 線程和進(jìn)程:下載為進(jìn)程,下載的多個(gè)視頻為線程,共享線程資源
- 共享變量:指的是多個(gè)線程都可以操作的變量
保存在堆
和方法區(qū)
中的變量就是Java中的共享變量
堆中存放new出來的對(duì)象,(包括實(shí)例變量); 棧中存放正在調(diào)用的方法中的局部變量 (包括方法的參數(shù)); 方法區(qū)中存儲(chǔ).class 字節(jié)碼 文件(包括 靜態(tài)變量 、靜態(tài)方法)
public class Variables { // 類變量 共享變量 private static int a; //成員變量 共享變量 private int b; //局部變量 c和d 非共享變量 public void test(int c){ int d; } } //多線程場(chǎng)景,對(duì)于變量a和b的操作是需要考慮線程安全的,而對(duì)于線程c和d的操作是不需要考慮線程安全的。
線程不安全
多線程并發(fā)執(zhí)行某個(gè)代碼時(shí),產(chǎn)生了邏輯上的錯(cuò)誤,結(jié)果和預(yù)期值不相同 線程安全是指多線程執(zhí)行時(shí)沒有產(chǎn)生邏輯錯(cuò)誤,結(jié)果和預(yù)期值相同
//開啟了兩個(gè)線程,每個(gè)線程執(zhí)行1000次循環(huán),循環(huán)中對(duì)count進(jìn)行加1操作。等待兩個(gè)線程都執(zhí)行完成后,打印count的值。 public class Test { private static int count; private static class Thread1 extends Thread { public void run() { for (int i = 0; i < 1000; i++) { count ++; try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread1 t1 = new Thread1(); Thread1 t2 = new Thread1(); t1.start(); t2.start(); //main主線程內(nèi)調(diào)用join()方法:休眠主線程,等待t1、t2線程執(zhí)行完畢主線程再繼續(xù),即最后輸出count值 t1.join(); t2.join(); System.out.println(count); } }
輸出結(jié)果應(yīng)該是2000但是不有時(shí)候1996
count++的指令在實(shí)際執(zhí)行的過程中不是原子性的,而是要分為讀、改、寫三步來進(jìn)行;即先從內(nèi)存中讀出count的值,然后執(zhí)行+1操作,再將結(jié)果寫回內(nèi)存中,如下圖所示。
上圖中線程1執(zhí)行了兩次自加操作,而線程2執(zhí)行了一次自加操作,但是count卻從6變成了8,只加了2。
我們看一下為什么會(huì)出現(xiàn)這種情況。當(dāng)線程1讀取count的值為6完成后,此時(shí)切換到了線程2執(zhí)行,線程2同樣讀取到了count的值為6,而后進(jìn)行改和寫操作,count的值變?yōu)榱?;此時(shí)線程又切回了線程1,但是線程1中count的值依然是線程2修改前的6,這就是問題所在!即線程2修改了count的值,但是這種修改對(duì)線程1不可見,導(dǎo)致了程序出現(xiàn)了線程不安全的問題,沒有符合我們預(yù)期的邏輯。
導(dǎo)致線程不安全的原因
主要有三點(diǎn):
- 不滿足原子性:一個(gè)或者多個(gè)操作在 CPU 執(zhí)行的過程中被中斷,當(dāng) cpu 執(zhí)行一個(gè)線程過程時(shí),調(diào)度器可能調(diào)走CPU,去執(zhí)行另一個(gè)線程,此線程的操作可能還沒有結(jié)束;(synchronized鎖解決)
- 不滿足可見性:一個(gè)線程對(duì)共享變量的修改,另外一個(gè)線程不能立刻看到
- 不滿足有序性:程序執(zhí)行的順序沒有按照代碼的先后順序執(zhí)行三、怎樣解決線程不安全
Java中的原子操作包括:
- 除long和double之外的基本類型的賦值操作
- 所有引用reference的賦值操作
- java.concurrent.Atomic.* 包中所有類的一切操作
可見性是指當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值。
volatile
關(guān)鍵字來保證可見性
。當(dāng)一個(gè)共享變量被volatile修飾時(shí),它會(huì)保證修改的值會(huì)立即被更新到主存,當(dāng)有其他線程需要讀取時(shí),它會(huì)去內(nèi)存中讀取新值。而普通的共享變量不能保證可見性,因?yàn)槠胀ü蚕碜兞勘恍薷闹?,什么時(shí)候被寫入主存是不確定的,當(dāng)其他線程去讀取時(shí),此時(shí)內(nèi)存中可能還是原來的舊值,因此無法保證可見性。另外,通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時(shí)刻只有一個(gè)線程獲取鎖然后執(zhí)行同步代碼,并且在釋放鎖之前會(huì)將對(duì)變量的修改刷新到主存當(dāng)中。因此可以保證可見性。
volatile
關(guān)鍵字來保證一定的“有序性”
。另外可以通過synchronized和Lock來保證有序性,很顯然,synchronized和Lock保證每個(gè)時(shí)刻是有一個(gè)線程執(zhí)行同步代碼,相當(dāng)于是讓線程順序執(zhí)行同步代碼,自然就保證了有序性。另外,Java內(nèi)存模型具備一些先天的“有序性”,即不需要通過任何手段就能夠得到保證的有序性,這個(gè)通常也稱為 happens-before 原則。如果兩個(gè)操作的執(zhí)行次序無法從happens-before原則推導(dǎo)出來,那么它們就不能保證它們的有序性,虛擬機(jī)可以隨意地對(duì)它們進(jìn)行重排序。
補(bǔ)充:happens-before原則(先行發(fā)生原則)
- 程序次序規(guī)則:一個(gè)線程內(nèi),按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作。
- 鎖定規(guī)則:一個(gè)unLock操作先行發(fā)生于后面對(duì)同一個(gè)鎖額lock操作。
- volatile變量規(guī)則:對(duì)一個(gè)變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作。
- 傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C。
- 線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每個(gè)一個(gè)動(dòng)作。
- 線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生。
- 線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測(cè),我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測(cè)到線程已經(jīng)終止執(zhí)行。
- 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于他的finalize()方法的開始。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot配置項(xiàng)目訪問路徑URL的根路徑方式
這篇文章主要介紹了SpringBoot配置項(xiàng)目訪問路徑URL的根路徑方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01解決maven打包排除類不生效maven-compiler-plugin問題
總結(jié):在Spring Boot項(xiàng)目B中作為項(xiàng)目A的依賴時(shí),排除啟動(dòng)類不生效的原因是被其他類引用或父POM引入,解決方法是跳過test編譯或注釋掉@SpringBootTest(classes={BApplication.class})2024-11-11區(qū)分java中String+String和String+char
這篇文章主要向大家詳細(xì)區(qū)分了java中String+String和String+char,感興趣的小伙伴們可以參考一下2016-01-01java9新特性Collection集合類的增強(qiáng)與優(yōu)化方法示例
這篇文章主要為大家介紹了java9新特性Collection集合類的增強(qiáng)與優(yōu)化方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03SpringBoot+Docker+IDEA實(shí)現(xiàn)一鍵構(gòu)建+推送、運(yùn)行、同鏡像多容器啟動(dòng)
這篇文章主要介紹了SpringBoot+Docker+IDEA實(shí)現(xiàn)一鍵構(gòu)建+推送、運(yùn)行、同鏡像多容器啟動(dòng),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04activiti實(shí)現(xiàn)員工請(qǐng)假流程解析
這篇文章主要介紹了activiti實(shí)現(xiàn)員工請(qǐng)假流程解析,本文通過實(shí)例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07