1.java內(nèi)存模型
注 : JAVA中的堆棧和內(nèi)存模型:,
1.1內(nèi)存模型:
Java內(nèi)存模型是圍繞著在并發(fā)過程中如何處理原子性蔑滓、可見性和有序性來建立的(三個特性詳解見10.)
a.Java內(nèi)存模型將內(nèi)存分為了主內(nèi)存和工作內(nèi)存
b.Java內(nèi)存模型規(guī)定所有的變量都存儲在主內(nèi)存中灰嫉,每個線程有自己的工作內(nèi)存
c.主內(nèi)存主要包括:堆和方法區(qū)拆宛,主內(nèi)存是所有線程共享的
d.工作內(nèi)存主要包括:該線程私有的棧和對主內(nèi)存部分變量拷貝的寄存器(包括程序計數(shù)器和cpu高速緩存區(qū))
e.Java內(nèi)存模型規(guī)定了所有變量都存儲在主內(nèi)存中,每個線程有自己的工作內(nèi)存讼撒,線程的工作內(nèi)存中保存了該線程使用到的變量到主內(nèi)存副本拷貝浑厚,線程對變量的所有操作都必須在自己的工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存中的變量根盒,不同線程之間也無法直接操作對方工作內(nèi)存中的變量钳幅,線程間變量值的傳遞需要通過主內(nèi)存來完成,
1.2什么情況下線程棧中的數(shù)據(jù)會刷新呢主存中的變量?**
①當變量被volatile關(guān)鍵字修飾時炎滞,對于共享資源的讀操作會直接在主內(nèi)存中進行(當然也會緩存到工作內(nèi)存中敢艰,當其他線程對該共享資源進行了修改,則會導致當前線程在工作內(nèi)存中的共享資源失效厂榛,所以必須從主內(nèi)存中再次獲雀墙谩)丽惭,對于共享資源的寫操作當然是先要修改工作內(nèi)存,但是修改結(jié)束后會立刻將其刷新到主內(nèi)存中辈双。
②通過synchronized關(guān)鍵字能夠保證可見性责掏,synchronized關(guān)鍵字能夠保證同一時刻只有一個線程獲得鎖,然后執(zhí)行同步方法湃望,并且還會確保在鎖釋放之前换衬,會將對變量的修改刷新到主內(nèi)存當中。JVM規(guī)范定義了線程對內(nèi)存間交互的八種操作:(待補充)
1.3堆,棧,方法區(qū)
1.椫ぐ牛空間(stack)瞳浦,連續(xù)的存儲空間,遵循后進先出的原則废士,存放基本類型的變量數(shù)據(jù)和對象的引用叫潦,但對象本身不存放在棧中,而是存放在堆(new 出來的對象)或者常量池中(字符串常量對象存放在常量池中官硝。); 當在一段代碼塊定義一個變量時矗蕊,Java在棧中為這個變量分配內(nèi)存空間,當該變量退出其作用域Ⅰ后氢架,Java會自動釋放掉為該變量所分配的內(nèi)存空間傻咖,該內(nèi)存空間可以立即被另作他用。
注:Ⅰ:變量的作用域:從變量定義的位置開始岖研,到該變量所在的那對大括號結(jié)束
Ⅱ:變量周期性: 從變量定義的位置開始就在內(nèi)存中活了卿操;到達它所在的作用域的時候就在內(nèi)存中消失了;
2.堆空間(heap)孙援,不連續(xù)的空間害淤,用于存放new出的對象,或者說是類的實例;當引用變量是普通的變量赃磨,定義時在棧中分配筝家,引用變量在程序運行到其作用域之外后被釋放。而數(shù)組和對象本身在堆中分配邻辉,即使程序運行到使用 new 產(chǎn)生數(shù)組或者對象的語句所在的代碼塊之外,數(shù)組和對象本身占據(jù)的內(nèi)存不會被釋放腮鞍,數(shù)組和對象在沒有引用變量指向它的時候值骇,才變?yōu)槔荒茉诒皇褂靡乒匀徽紦?jù)內(nèi)存空間不放吱瘩,在隨后的一個不確定的時間被垃圾回收器收走(釋放掉)。這也是 Java 比較占內(nèi)存的原因迹缀。
實際上使碾,棧中的變量指向堆內(nèi)存中的變量蜜徽,這就是Java中的指針!
3.堆與棧:堆是由垃圾回收來負責的票摇,堆的優(yōu)勢是可以動態(tài)地分配內(nèi)存 大小拘鞋,生存期也不必事先告訴編譯器,因為它是在運行時動態(tài)分配內(nèi)存的矢门,Java的垃圾收集器會自動收走這些不再使用的數(shù)據(jù)盆色。但缺點是,由于要在運行時動態(tài) 分配內(nèi)存祟剔,存取速度較慢隔躲。
棧的優(yōu)勢是,存取速度比堆要快物延,僅次于寄存器宣旱,棧數(shù)據(jù)可以共享(int a = 3再int b = 3此時內(nèi)存中值存在一個3,a,b兩個引用同時指向同一個3)。但缺點是叛薯,存在棧中的數(shù)據(jù)大小與生存期必須是確定的浑吟,缺乏靈活性。對于棧和常量池中的對象Ⅰ可以共享案训,對于堆中的對象不可以共享买置。棧中的數(shù)據(jù)大小和生命周期是可以確定的。堆中的對象的由垃圾回收器負責回收强霎,因此大小和生命周期不需要確定 忿项,具有很大的靈活性。
注:Ⅰ:用new來生成的對象都是放在堆中的城舞,直接定義的局部變量都是放在棧中的轩触,全局和靜態(tài)的對象是放在數(shù)據(jù)段的靜態(tài)存儲區(qū),例如: Class People家夺;People p;//棧上分配內(nèi)存People* pPeople脱柱;pPeople = new People;//堆上分配內(nèi)存
對于字符串:其對象的引用都是存儲在棧中的,如果是 編譯期已經(jīng)創(chuàng)建好(直接用雙引號定義的)的就存儲在常量池中拉馋,如果是運行期(new出來的)才確定的就存儲在堆中 榨为。對于equals相等的字符串,在常量池中永遠只有一份煌茴,在堆中有多份随闺。
4.方法區(qū)(method),方法區(qū)在堆空間內(nèi)蔓腐,用于存放 ①類的代碼信息掷伙;②靜態(tài)變量和方法杉适;③常量池(字符串常量和基本類型常量(public static final)罚拟,具有共享機制);常量池指的是在編譯期被確定躁倒,并被保存在已編譯的.class文件中的一些數(shù)據(jù)。除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數(shù)組)的常量值(final)還包含一些以文本形式出現(xiàn)的符號引用,比如:類和接口的全限定名;字段的名稱和描述符职抡;方法和名稱和描述符。
Java中除了基本數(shù)據(jù)類型硫椰,其他的均是引用類型繁调,包括類、數(shù)組等等靶草。
2.Java 線程
2.1.進程和線程的區(qū)別是什么蹄胰?
線程是操作系統(tǒng)能夠進行運算調(diào)度的最小單位也是進程中的實際運作單位。一個進程可以有很多線程奕翔,每條線程并行執(zhí)行不同的任務裕寨。不同的進程使用不同的內(nèi)存空間,而當前進程下的所有線程共享一片相同的內(nèi)存空間派继。 每個線程都擁有單獨的棧內(nèi)存用來存儲本地數(shù)據(jù) .
2.2線程的幾種可用狀態(tài)宾袜。
線程在執(zhí)行過程中,可以處于下面幾種狀態(tài):
就緒(Runnable):線程準備運行驾窟,不一定立馬就能開始執(zhí)行庆猫。
運行中(Running):進程正在執(zhí)行線程的代碼。
等待中(Waiting):線程處于阻塞的狀態(tài)绅络,等待外部的處理結(jié)束月培。
睡眠中(Sleeping):線程被強制睡眠。
I/O 阻塞(Blocked on I/O):等待 I/O 操作完成恩急。
同步阻塞(Blocked on Synchronization):等待獲取鎖杉畜。
死亡(Dead):線程完成了執(zhí)行。
2.3.創(chuàng)建線程的幾種不同的方式
有三種方式可以用來創(chuàng)建線程:
2.3.1繼承 Thread 類 :通過繼承Thread實現(xiàn)的線程類衷恭,多個線程間無法共享線程類的實例變量此叠。
public class ThreadTest extends Thread {
private int ticket = 10;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (this) {
if (this.ticket > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "賣票---->" + (this.ticket--));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] arg) {
ThreadTest t1 = new ThreadTest();
new Thread(t1, "線程1").start();
new Thread(t1, "線程2").start();
//也達到了資源共享的目的然而事實卻不盡如此。
}
}
2.3.2實現(xiàn) Runnable 接口 : 這種方式更受歡迎随珠,因為這不需要繼承 Thread 類灭袁。在應用設計中已經(jīng)繼 承了別的對象的情況下,這需要多繼承(而 Java 不支持多繼承)窗看,只能實現(xiàn)接口简卧。
public class RunnableTest implements Runnable {
private int ticket = 10;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//添加同步快
synchronized (this) {
if (this.ticket > 0) {
try {
//通過睡眠線程來模擬出最后一張票的搶票場景
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "賣票---->" + (this.ticket--));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] arg) {
RunnableTest t1 = new RunnableTest();
new Thread(t1, "線程1").start();
new Thread(t1, "線程2").start();
}
}
2.3.3 實現(xiàn)Callable接口 : 是Runnable接口的增強版,使用call()方法作為線程的執(zhí)行體增強了之前的run()方法,因為call()方法有返回值,也可以聲明拋出異常;
MyTask.java類
FutureTask使用方法:
Callable接口與Runnable接口對比:
1.Callable規(guī)定的方法是call(),而Runnable規(guī)定的方法是run().
2.Callable的任務執(zhí)行后可返回值烤芦,而Runnable的任務是不能返回值的。
3.call() 方法可拋出異常析校,而run() 方法是不能拋出異常的构罗。
運行Callable任務可拿到一個FutureTask對象铜涉, FutureTask表示異步計算的結(jié)果
3.線程池
3.1線程池介紹 :
線程池就是首先創(chuàng)建一些線程,它們的集合稱為線程池遂唧。使用線程池可以很好地提高性能芙代,線程池在系統(tǒng)啟動時即創(chuàng)建大量空閑的線程,程序?qū)⒁粋€任務傳給線程池盖彭,線程池就會啟動一條線程來執(zhí)行這個任務纹烹,執(zhí)行結(jié)束以后,該線程并不會死亡召边,而是再次返回線程池中成為空閑狀態(tài)铺呵,等待執(zhí)行下一個任務。
3.2線程池的工作機制 :
在線程池的工作模式下,任務是整個提交給線程池的隧熙,而不是直接提交給某個線程片挂,線程池在拿到任務后,就在內(nèi)部尋找是否有空閑的線程贞盯,如果有音念,則將任務交給某個空閑的線程。一個線程同時只能執(zhí)行一個任務躏敢,但可以同時向一個線程池提交多個任務闷愤。
3.3使用線程池的原因 :
降低資源的消耗
通過重復利用已經(jīng)創(chuàng)建好的線程降低線程創(chuàng)建和銷毀帶來的損耗
提高響應速度
線程池中的線程沒有超過上限時,有線程處于等待分配任務的狀態(tài),當任務來時無需創(chuàng)建線程這一步驟就能直接執(zhí)行。
提高線程的可管理性
線程池里提供了操作線程的方法,這就為對管理線程提供了可能性件余。
3.4四種常見的線程池詳解 :
線程池的返回值ExecutorService: 是Java提供的用于管理線程池的類讥脐。該類的兩個作用:控制線程數(shù)量和重用線程
我們在實際業(yè)務中,以上三種線程啟動的方式都不用。 將所有的多線程異步任務都交給線程池
3.4.1 原生線程池
創(chuàng)建
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor();
原生線程池的七大參數(shù)
corePoolSize : 核心線程數(shù)(一致存在除非設置了allowThreadTimeOut),線程池創(chuàng)建好之后就等待來接受異步任務去執(zhí)行蛾扇。
maximumPoolSize : 最大線程數(shù),控制資源并發(fā)
keepAliveTime : 存活時間,如果當前線程數(shù)量大于核心線程數(shù),且線程空閑的時間大于指定的keepAliveTime就會釋放線程(不會釋放核心線程)
unit : 指定存活時間的時間單位
BlockingQueue workQueue : 阻塞隊列的最大數(shù)量(該值的大小有壓力測試后的峰值決定),如果任務數(shù)大于maximumPoolSize,就會將任務放在隊列里,只要有線程空閑就會去隊列里去除新的任務執(zhí)行攘烛。
ThreadFactory threadFactory : 線程的創(chuàng)建工廠。
RejectedExecutionHandler handler : 如果workQueue滿了按照指定的拒絕策略拒絕執(zhí)行任務
運行流程
1.線程池創(chuàng)建,準備好core數(shù)量的核心線程,準備接受任務镀首。
2.新的任務進來用core準備好的空閑線程執(zhí)行坟漱。
(1)如果core滿了,就將再進來的任務放入阻塞隊列中,空閑的core就會自己去阻塞隊列獲取任務執(zhí)行
(2)如果阻塞隊列滿了,就直接開新線程執(zhí)行,最大只能開到max指定的數(shù)量
(3)max任務都執(zhí)行好了。Max減去core的數(shù)量的空閑線程會在keepAliveTime 指定的時間后自動銷毀更哄。最終保持到core大小
(4)如果線程數(shù)開到max的數(shù)量還不夠用就是用RejectedExecutionHandler 指定的拒絕策略進行處理芋齿。
3.所有的線程都是由指定的factory創(chuàng)建
面試提
3.4.2種常用的線程池(返回值都是ExecutorService)
3.4.2.1 Executors.newCacheThreadPool():
可緩存線程池,core的數(shù)量為0,所有都可以回收, 先查看池中有沒有以前建立的線程成翩,如果有觅捆,就直接使用。如果沒有麻敌,就建一個新的線程加入池中栅炒,緩存型池子通常用于執(zhí)行一些生存期很短的異步型任務(運行結(jié)果見下匯總圖).
線程池為無限大,當執(zhí)行當前任務時上一個任務已經(jīng)完成,會復用執(zhí)行上一個任務的線程赢赊,而不用每次新建線程
3.4.2.2 Executors.newFixedThreadPool(int n):
創(chuàng)建一個可重用固定個數(shù)的線程池,core的數(shù)量為max,都不可以回收乙漓,以共享的無界隊列方式來運行這些線程。(運行結(jié)果見下匯總圖).
3.4.2.3 Executors.newScheduledThreadPool(int n):創(chuàng)建一個定長線程池释移,支持定時及周期性任務執(zhí)行(運行結(jié)果見下匯總圖).
3.4.2.4 Executors.newSingleThreadExecutor():
創(chuàng)建一個單線程化的線程池,從阻塞隊列里挨個獲取任務叭披,它只會用唯一的工作線程來執(zhí)行任務,保證所有任務按照指定順序(FIFO , LIFO,優(yōu)先級)執(zhí)行(運行結(jié)果見下匯總圖).
以上的所有execute都可以使用submit代替,并且submit可以有返回值
4.CompletableFuture異步編排
4.1創(chuàng)建異步對象
CompletableFuture提供了四個靜態(tài)方法來創(chuàng)建一個異步操作玩讳。
Supplier supplier : 參數(shù)為一個方法
Executor executor可以傳入自定義線程池,否則使用自己默認的線程池
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
給出一個例子
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService future= Executors.newFixedThreadPool(10);
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("當前線程------------------" + Thread.currentThread().getName());
int i = 10 / 2;
return i;
}, future);
//獲取異步執(zhí)行的結(jié)果在線程執(zhí)任務行完之后返回
Integer integer = integerCompletableFuture.get();
System.out.println("結(jié)果為"+integer);//結(jié)果為5
}
4.2whenComplete(計算完成時回調(diào)方法)
CompletableFuture提供了四個方法計算完成時回調(diào)方法
public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)
下面給出一個例子
//方法執(zhí)行完成后的感知
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService future= Executors.newFixedThreadPool(10);
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("當前線程------------------" + Thread.currentThread().getName());
int i = 10 / 0;//使用int i = 10 / 0;模擬有異常
return i;
}, future).whenComplete((ems,exception)->{
//在出現(xiàn)異常時雖然可以感知異常但不能修改數(shù)據(jù)
System.out.println("計算結(jié)果為:"+ems+"----異常為"+exception);
}).exceptionally((throwable)->{
//可以感知異常,并可以返回結(jié)果
return 10;
});
Integer integer = integerCompletableFuture.get();
System.out.println(integer);
}
whenComplete可以感知正常和異常的計算結(jié)果,無異常時直接返回結(jié)果,在感知到異常時使用exceptionally處理異常情況
whenComplete和whenCompleteAsync的區(qū)別:
whenComplete : 是執(zhí)行當前任務的線程執(zhí)行繼續(xù)執(zhí)行whenComplete的任務
whenCompleteAsync :是執(zhí)行把whenCompleteAsync這個任務繼續(xù)提交給線程池來執(zhí)行
方法不以Async結(jié)尾,意味著Action使用相同的線程執(zhí)行,而Async可能會使用其他線程執(zhí)行(如果是使用相同的線程池也可能會被同一個線程選中執(zhí)行)
4.3handle方法(處理異常返回結(jié)果)
public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor)
下面給出一個例子
//方法完成后的處理
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService future= Executors.newFixedThreadPool(10);
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("當前線程------------------" + Thread.currentThread().getName());
int i = 10 / 2;//使用int i = 10 / 0;模擬有異常
return i;
}, future).handle((result,throwable)->{
if(result!=null){
return result*2;
}
if(throwable!=null){
return result*0;
}
return 0;
});
Integer integer = integerCompletableFuture.get();
System.out.println(integer);
}
和whenComplete一樣,可以對結(jié)果做最后的處理(可處理異常),可改變返回值涩蜘。
4.4線程串行化方法(完成上步執(zhí)行其他任務)
public CompletableFuture<Void> thenRun(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action,Executor executor)
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor)
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
thenRun:處理完任務后執(zhí)行thenRun后面的方法
thenAccept:消費處理結(jié)果。接受任務的執(zhí)行結(jié)果并消費處理,無返回結(jié)果
thenApply:當一個線程依賴另一個線程時,獲取上一個任務返回的結(jié)果,并返回當前任務的返回值
以上所有都要前置任務完成
給出一個例子
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService future= Executors.newFixedThreadPool(10);
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("當前線程------------------" + Thread.currentThread().getName());
int i = 10 / 2;
return i;
}, future).thenApplyAsync(res -> {
return res+3;
}, future);
Integer integer = integerCompletableFuture.get();//此處結(jié)果為8
System.out.println(integer);
}
thenRun不能獲取到上一步執(zhí)行結(jié)果
thenAccept:能接受上一步執(zhí)行結(jié)果但沒返回值
thenApply:既能接受上一步執(zhí)行結(jié)果也有返回值
4.5將兩任務組合-(都完成時,才執(zhí)行作為參數(shù)傳入的方法)
public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,Runnable action)
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action)
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor)
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor)
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor)
給出一個例子
4.6將兩任務組合-(一個完成,才執(zhí)行作為參數(shù)傳入的方法)
public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,Runnable action)
public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action)
public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor)
public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action,Executor executor)
public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn,Executor executor)
給出一個例子
4.7多任務組合
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
allOf : 阻塞線程,等待所有任務完成,才繼續(xù)往下進行否則阻塞
anyOf : 阻塞線程,只要有一個任務完成,就繼續(xù)往下進行
給出一個例子
5.synchronized關(guān)鍵字與Lock
5.1 synchronized關(guān)鍵字:
Java 語言中熏纯,每個對象有一把鎖同诫。線程可以使用synchronized關(guān)鍵字獲取對象上的鎖。
同步代碼塊:
synchronized(鎖對象){ 需要同步的代碼 }
此處的所對象必須是存在堆中(多個線程的共享資源)的對象
同步方法:
權(quán)限關(guān)鍵字 synchronized 返回值 方法名(){ 需要被同步的代碼塊 }
同步方法的鎖對象是this
靜態(tài)方法及鎖對象問題: 鎖對象是類的字節(jié)碼文件對象(類的class文件)
5.2鎖的釋放時機:
① 當前線程的同步方法豆巨、代碼塊執(zhí)行結(jié)束的時候釋放
② 當前線程在同步方法剩辟、同步代碼塊中遇到break、return 終于該代碼塊或者方法的時候釋放往扔。
③ 當前線程出現(xiàn)未處理的error或者exception導致異常結(jié)束的時候釋放贩猎。
④ 程序執(zhí)行了同步對象wait方法,當前線程暫停萍膛,釋放鎖吭服。
5.3 lock和ReadWriteLock:
兩大鎖的根接口,Lock代表實現(xiàn)類是ReentrantLock(可重入鎖)蝗罗,ReadWriteLock(讀寫鎖)的代表實現(xiàn)類是ReentrantReadWriteLock艇棕。
5.3.1Lock相較synchronized的優(yōu)點:
①synchronized實現(xiàn)同步線程(IO讀文件時)阻塞不釋放鎖時,其他線程需一直等待,Lock可以通過只等待一定的時間 (tryLock(long time, TimeUnit unit)) 或者能夠響應中斷(lockInterruptibly())解決。
②多個線程讀寫文件時,讀1操作與讀2操作不會起沖突synchronized實現(xiàn)的同步的話也只有一個線程在執(zhí)行讀1操作.讀2操作需等待,Lock可以解決這種情況 (ReentrantReadWriteLock)串塑。
③可以通過Lock得知線程有沒有成功獲取到鎖 (解決方案:ReentrantLock) 沼琉,但這個是synchronized無法辦到的。
5.3.2lock中的方法: Lock lock = new ReentranLock;
lock();用來獲取鎖桩匪。如果鎖已被其他線程獲取打瘪,則進行等待;必須在try…catch…塊中進行,并且將釋放鎖的操作放在finally塊中進行傻昙,
*tryLock()??嘗試獲取鎖闺骚,獲取成功返回true;獲取失斪钡怠(鎖已被其他線程獲绕),返回false贾惦,這個方法無論如何都會立即返回(在拿不到鎖時不會一直在那等待)
*tryLock(long time, TimeUnit unit)??拿不到鎖時會等待一定的時間胸梆,在時間期限之內(nèi)如果還拿不到鎖敦捧,就返回false,同時可以響應中斷乳绕。拿到鎖绞惦,則返回true。
*lockInterruptibly()??當通過這個方法去獲取鎖時洋措,如果其他線程正在等待獲取鎖,則這個線程能夠響應中斷杰刽,即中斷線程的等待狀態(tài)菠发。也就使說,當兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時贺嫂,假若此時線程A獲取到了鎖滓鸠,而線程B只有等待,那么對線程B調(diào)用threadB.interrupt()方法能夠中斷線程B的等待過程第喳。
interrupt()方法只能中斷阻塞過程中的線程而不能中斷正在運行過程中的線程糜俗。
unlock:釋放鎖在finally語句塊中執(zhí)行。
5.3.3ReadWriteLock中的方法:ReadWriteLock rl= new ReentrantReadWriteLock();**
維護了一對相關(guān)的鎖曲饱,一個用于只讀操作悠抹,另一個用于寫入操作。只要沒有 writer扩淀,讀取鎖可以由多個 reader 線程同時保持楔敌,而寫入鎖是獨占的。
rl.readLock();//返回Lock接口可通過Lock接口內(nèi)方法獲取鎖
rl.writeLock();//返回Lock接口可通過Lock接口內(nèi)方法獲取鎖
6.volatile
6.1volatile簡介
volatile用以聲明變量的值可能隨時會別的線程修改驻谆,使用volatile修飾的變量會強制將修改的值立即寫入主存卵凑,主存中值的更新會使緩存中的值失效(非volatile變量不具備這樣的特性,非volatile變量的值會被緩存胜臊,線程A更新了這個值勺卢,線程B讀取這個變量的值時可能讀到的并不是是線程A更新后的值)。volatile會禁止指令重排象对。
6.2volatile特性
volatile具有可見性黑忱、有序性,不具備原子性织盼。
注意杨何,volatile不具備原子性,這是volatile與java中的synchronized沥邻、java.util.concurrent.locks.Lock最大的功能差異危虱,這一點在面試中也是非常容易問到的點。
原子性:原子性通常指多個操作不存在只執(zhí)行一部分的情況唐全,要么全部執(zhí)行要么全部失敗
可見性:當多個線程訪問同一個變量x時埃跷,線程1修改了變量x的值蕊玷,線程1、線程2…線程n能夠立即讀取到線程1修改后的值弥雹。
有序性:即程序執(zhí)行時按照代碼書寫的先后順序執(zhí)行垃帅。在Java內(nèi)存模型中,允許編譯器和處理器對指令進行重排序剪勿,但是重排序過程不會影響到單線程程序的執(zhí)行贸诚,卻會影響到多線程并發(fā)執(zhí)行的正確性。
那么可能的一個執(zhí)行順序是:語句2 -> 語句1 -> 語句3 -> 語句4
那么可不可能是這個執(zhí)行順序: 語句2 -> 語句1 -> 語句4 -> 語句3厕吉。
不可能酱固,因為處理器在進行重排序時是會考慮指令之間的數(shù)據(jù)依賴性,如果一個指令Instruction 2必須用到Instruction 1的結(jié)果头朱,那么處理器會保證Instruction 1會在Instruction 2之前執(zhí)行运悲。重排序不會影響單個線程內(nèi)程序執(zhí)行的結(jié)果,但在多線程處理器不能保證