多線程異步編排和線程池

1.java內存模型

注 : JAVA中的堆棧和內存模型:,

1.1內存模型:

Java內存模型是圍繞着在併發過程中如何處理原子性、可見性和有序性來建立的(三個特性詳解見10.)
a.Java內存模型將內存分爲了主內存和工作內存
b.Java內存模型規定所有的變量都存儲在主內存中,每個線程有自己的工作內存
c.主內存主要包括:堆和方法區,主內存是所有線程共享的
d.工作內存主要包括:該線程私有的棧和對主內存部分變量拷貝的寄存器(包括程序計數器和cpu高速緩存區)
e.Java內存模型規定了所有變量都存儲在主內存中,每個線程有自己的工作內存,線程的工作內存中保存了該線程使用到的變量到主內存副本拷貝,線程對變量的所有操作都必須在自己的工作內存中進行,而不能直接讀寫主內存中的變量,不同線程之間也無法直接操作對方工作內存中的變量,線程間變量值的傳遞需要通過主內存來完成,

1.2什麼情況下線程棧中的數據會刷新呢主存中的變量?**

①當變量被volatile關鍵字修飾時,對於共享資源的讀操作會直接在主內存中進行(當然也會緩存到工作內存中,當其他線程對該共享資源進行了修改,則會導致當前線程在工作內存中的共享資源失效,所以必須從主內存中再次獲取),對於共享資源的寫操作當然是先要修改工作內存,但是修改結束後會立刻將其刷新到主內存中。

②通過synchronized關鍵字能夠保證可見性,synchronized關鍵字能夠保證同一時刻只有一個線程獲得鎖,然後執行同步方法,並且還會確保在鎖釋放之前,會將對變量的修改刷新到主內存當中。JVM規範定義了線程對內存間交互的八種操作:(待補充)

1.3堆,棧,方法區

1.棧空間(stack),連續的存儲空間,遵循後進先出的原則,存放基本類型的變量數據和對象的引用,但對象本身不存放在棧中,而是存放在堆(new 出來的對象)或者常量池中(字符串常量對象存放在常量池中。); 當在一段代碼塊定義一個變量時,Java在棧中爲這個變量分配內存空間,當該變量退出其作用域Ⅰ後,Java會自動釋放掉爲該變量所分配的內存空間,該內存空間可以立即被另作他用。
注:Ⅰ:變量的作用域:從變量定義的位置開始,到該變量所在的那對大括號結束
Ⅱ:變量週期性: 從變量定義的位置開始就在內存中活了;到達它所在的作用域的時候就在內存中消失了;

2.堆空間(heap),不連續的空間,用於存放new出的對象,或者說是類的實例;當引用變量是普通的變量,定義時在棧中分配,引用變量在程序運行到其作用域之外後被釋放。而數組和對象本身在堆中分配,即使程序運行到使用 new 產生數組或者對象的語句所在的代碼塊之外,數組和對象本身佔據的內存不會被釋放,數組和對象在沒有引用變量指向它的時候,才變爲垃圾,不能在被使用,但仍然佔據內存空間不放,在隨後的一個不確定的時間被垃圾回收器收走(釋放掉)。這也是 Java 比較佔內存的原因。
  實際上,棧中的變量指向堆內存中的變量,這就是Java中的指針!

**3.堆與棧:**堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存 大小,生存期也不必事先告訴編譯器,因爲它是在運行時動態分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由於要在運行時動態 分配內存,存取速度較慢。

棧的優勢是,存取速度比堆要快,僅次於寄存器,棧數據可以共享(int a = 3再int b = 3此時內存中值存在一個3,a,b兩個引用同時指向同一個3)。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。對於棧和常量池中的對象Ⅰ可以共享,對於堆中的對象不可以共享。棧中的數據大小和生命週期是可以確定的。堆中的對象的由垃圾回收器負責回收,因此大小和生命週期不需要確定 ,具有很大的靈活性。
注:Ⅰ:用new來生成的對象都是放在堆中的,直接定義的局部變量都是放在棧中的,全局和靜態的對象是放在數據段的靜態存儲區,例如: Class People;People p;//棧上分配內存People* pPeople;pPeople = new People;//堆上分配內存
對於字符串:其對象的引用都是存儲在棧中的,如果是 編譯期已經創建好(直接用雙引號定義的)的就存儲在常量池中,如果是運行期(new出來的)才確定的就存儲在堆中 。對於equals相等的字符串,在常量池中永遠只有一份,在堆中有多份。

4.方法區(method),方法區在堆空間內,用於存放 ①類的代碼信息;②靜態變量和方法;③常量池(字符串常量和基本類型常量(public static final),具有共享機制);常量池指的是在編譯期被確定,並被保存在已編譯的.class文件中的一些數據。除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數組)的常量值(final)還包含一些以文本形式出現的符號引用,比如:類和接口的全限定名;字段的名稱和描述符;方法和名稱和描述符。
Java中除了基本數據類型,其他的均是引用類型,包括類、數組等等。

2.Java 線程

2.1.進程和線程的區別是什麼?

線程是操作系統能夠進行運算調度的最小單位也是進程中的實際運作單位。一個進程可以有很多線程,每條線程並行執行不同的任務。不同的進程使用不同的內存空間,而當前進程下的所有線程共享一片相同的內存空間。 每個線程都擁有單獨的棧內存用來存儲本地數據 .
在這裏插入圖片描述

2.2線程的幾種可用狀態。

線程在執行過程中,可以處於下面幾種狀態:
就緒(Runnable):線程準備運行,不一定立馬就能開始執行。
運行中(Running):進程正在執行線程的代碼。
等待中(Waiting):線程處於阻塞的狀態,等待外部的處理結束。
睡眠中(Sleeping):線程被強制睡眠。
I/O 阻塞(Blocked on I/O):等待 I/O 操作完成。
同步阻塞(Blocked on Synchronization):等待獲取鎖。
死亡(Dead):線程完成了執行。

2.3.創建線程的幾種不同的方式

有三種方式可以用來創建線程:

2.3.1繼承 Thread 類 :通過繼承Thread實現的線程類,多個線程間無法共享線程類的實例變量。

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實現 Runnable 接口 : 這種方式更受歡迎,因爲這不需要繼承 Thread 類。在應用設計中已經繼 承了別的對象的情況下,這需要多繼承(而 Java 不支持多繼承),只能實現接口。

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 實現Callable接口 : 是Runnable接口的增強版,使用call()方法作爲線程的執行體增強了之前的run()方法,因爲call()方法有返回值,也可以聲明拋出異常;

MyTask.java類
在這裏插入圖片描述
FutureTask使用方法:
在這裏插入圖片描述

Callable接口與Runnable接口對比:
1.Callable規定的方法是call(),而Runnable規定的方法是run().
2.Callable的任務執行後可返回值,而Runnable的任務是不能返回值的。
3.call() 方法可拋出異常,而run() 方法是不能拋出異常的。
運行Callable任務可拿到一個FutureTask對象, FutureTask表示異步計算的結果

3.線程池

3.1線程池介紹 :

線程池就是首先創建一些線程,它們的集合稱爲線程池。使用線程池可以很好地提高性能,線程池在系統啓動時即創建大量空閒的線程,程序將一個任務傳給線程池,線程池就會啓動一條線程來執行這個任務,執行結束以後,該線程並不會死亡,而是再次返回線程池中成爲空閒狀態,等待執行下一個任務。

3.2線程池的工作機制 :

在線程池的工作模式下,任務是整個提交給線程池的,而不是直接提交給某個線程,線程池在拿到任務後,就在內部尋找是否有空閒的線程,如果有,則將任務交給某個空閒的線程。一個線程同時只能執行一個任務,但可以同時向一個線程池提交多個任務。

3.3使用線程池的原因 :

降低資源的消耗
通過重複利用已經創建好的線程降低線程創建和銷燬帶來的損耗
提高響應速度
線程池中的線程沒有超過上限時,有線程處於等待分配任務的狀態,當任務來時無需創建線程這一步驟就能直接執行。
提高線程的可管理性
線程池裏提供了操作線程的方法,這就爲對管理線程提供了可能性。

3.4四種常見的線程池詳解 :

線程池的返回值ExecutorService: 是Java提供的用於管理線程池的類。該類的兩個作用:控制線程數量和重用線程
我們在實際業務中,以上三種線程啓動的方式都不用。 將所有的多線程異步任務都交給線程池

3.4.1 原生線程池

創建
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor();

原生線程池的七大參數
corePoolSize : 核心線程數(一致存在除非設置了allowThreadTimeOut),線程池創建好之後就等待來接受異步任務去執行。
maximumPoolSize : 最大線程數,控制資源併發
keepAliveTime : 存活時間,如果當前線程數量大於核心線程數,且線程空閒的時間大於指定的keepAliveTime就會釋放線程(不會釋放核心線程)
unit : 指定存活時間的時間單位
BlockingQueue workQueue : 阻塞隊列的最大數量(該值的大小有壓力測試後的峯值決定),如果任務數大於maximumPoolSize,就會將任務放在隊列裏,只要有線程空閒就會去隊列裏去除新的任務執行。
ThreadFactory threadFactory : 線程的創建工廠。
RejectedExecutionHandler handler : 如果workQueue滿了按照指定的拒絕策略拒絕執行任務

運行流程
1.線程池創建,準備好core數量的核心線程,準備接受任務。
2.新的任務進來用core準備好的空閒線程執行。
(1)如果core滿了,就將再進來的任務放入阻塞隊列中,空閒的core就會自己去阻塞隊列獲取任務執行
(2)如果阻塞隊列滿了,就直接開新線程執行,最大隻能開到max指定的數量
(3)max任務都執行好了。Max減去core的數量的空閒線程會在keepAliveTime 指定的時間後自動銷燬。最終保持到core大小
(4)如果線程數開到max的數量還不夠用就是用RejectedExecutionHandler 指定的拒絕策略進行處理。
3.所有的線程都是由指定的factory創建
面試提
在這裏插入圖片描述

3.4.2種常用的線程池(返回值都是ExecutorService)

3.4.2.1 Executors.newCacheThreadPool():

可緩存線程池,core的數量爲0,所有都可以回收, 先查看池中有沒有以前建立的線程,如果有,就直接使用。如果沒有,就建一個新的線程加入池中,緩存型池子通常用於執行一些生存期很短的異步型任務(運行結果見下彙總圖).
在這裏插入圖片描述
在這裏插入圖片描述
線程池爲無限大,當執行當前任務時上一個任務已經完成,會複用執行上一個任務的線程,而不用每次新建線程

3.4.2.2 Executors.newFixedThreadPool(int n):

創建一個可重用固定個數的線程池,core的數量爲max,都不可以回收,以共享的無界隊列方式來運行這些線程。(運行結果見下彙總圖).
在這裏插入圖片描述
在這裏插入圖片描述

3.4.2.3 Executors.newScheduledThreadPool(int n):創建一個定長線程池,支持定時及週期性任務執行(運行結果見下彙總圖).

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

3.4.2.4 Executors.newSingleThreadExecutor():

創建一個單線程化的線程池,從阻塞隊列裏挨個獲取任務,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO , LIFO,優先級)執行(運行結果見下彙總圖).
在這裏插入圖片描述
在這裏插入圖片描述
以上的所有execute都可以使用submit代替,並且submit可以有返回值

4.CompletableFuture異步編排

在這裏插入圖片描述

4.1創建異步對象

CompletableFuture提供了四個靜態方法來創建一個異步操作。
Supplier supplier : 參數爲一個方法
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);
        //獲取異步執行的結果在線程執任務行完之後返回
        Integer integer = integerCompletableFuture.get();
        System.out.println("結果爲"+integer);//結果爲5
    }

4.2whenComplete(計算完成時回調方法)

CompletableFuture提供了四個方法計算完成時回調方法

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)

下面給出一個例子

//方法執行完成後的感知
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)->{
            //在出現異常時雖然可以感知異常但不能修改數據
            System.out.println("計算結果爲:"+ems+"----異常爲"+exception);
        }).exceptionally((throwable)->{
            //可以感知異常,並可以返回結果
            return 10;
        });
        Integer integer = integerCompletableFuture.get();
        System.out.println(integer);

    }

whenComplete可以感知正常和異常的計算結果,無異常時直接返回結果,在感知到異常時使用exceptionally處理異常情況
whenComplete和whenCompleteAsync的區別:
whenComplete : 是執行當前任務的線程執行繼續執行whenComplete的任務
whenCompleteAsync :是執行把whenCompleteAsync這個任務繼續提交給線程池來執行
方法不以Async結尾,意味着Action使用相同的線程執行,而Async可能會使用其他線程執行(如果是使用相同的線程池也可能會被同一個線程選中執行)

4.3handle方法(處理異常返回結果)

    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一樣,可以對結果做最後的處理(可處理異常),可改變返回值。

4.4線程串行化方法(完成上步執行其他任務)

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:處理完任務後執行thenRun後面的方法
thenAccept:消費處理結果。接受任務的執行結果並消費處理,無返回結果
thenApply:當一個線程依賴另一個線程時,獲取上一個任務返回的結果,並返回當前任務的返回值
以上所有都要前置任務完成

給出一個例子

 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();//此處結果爲8
        System.out.println(integer);
    }

thenRun不能獲取到上一步執行結果
thenAccept:能接受上一步執行結果但沒返回值
thenApply:既能接受上一步執行結果也有返回值

4.5將兩任務組合-(都完成時,才執行作爲參數傳入的方法)

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將兩任務組合-(一個完成,才執行作爲參數傳入的方法)

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 : 阻塞線程,等待所有任務完成,才繼續往下進行否則阻塞
anyOf : 阻塞線程,只要有一個任務完成,就繼續往下進行

給出一個例子
在這裏插入圖片描述

5.synchronized關鍵字與Lock

5.1 synchronized關鍵字:

Java 語言中,每個對象有一把鎖。線程可以使用synchronized關鍵字獲取對象上的鎖。
同步代碼塊:
synchronized(鎖對象){ 需要同步的代碼 }
此處的所對象必須是存在堆中(多個線程的共享資源)的對象
同步方法:
權限關鍵字 synchronized 返回值 方法名(){ 需要被同步的代碼塊 }
同步方法的鎖對象是this
靜態方法及鎖對象問題: 鎖對象是類的字節碼文件對象(類的class文件)

5.2鎖的釋放時機:

① 當前線程的同步方法、代碼塊執行結束的時候釋放
② 當前線程在同步方法、同步代碼塊中遇到break、return 終於該代碼塊或者方法的時候釋放。
③ 當前線程出現未處理的error或者exception導致異常結束的時候釋放。
④ 程序執行了同步對象wait方法,當前線程暫停,釋放鎖。

5.3 lock和ReadWriteLock:

兩大鎖的根接口,Lock代表實現類是ReentrantLock(可重入鎖),ReadWriteLock(讀寫鎖)的代表實現類是ReentrantReadWriteLock。

5.3.1Lock相較synchronized的優點:

①synchronized實現同步線程(IO讀文件時)阻塞不釋放鎖時,其他線程需一直等待,Lock可以通過只等待一定的時間 (tryLock(long time, TimeUnit unit)) 或者能夠響應中斷(lockInterruptibly())解決。

②多個線程讀寫文件時,讀1操作與讀2操作不會起衝突synchronized實現的同步的話也只有一個線程在執行讀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)😗*拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false,同時可以響應中斷。拿到鎖,則返回true。

**lockInterruptibly()😗*當通過這個方法去獲取鎖時,如果其他線程正在等待獲取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀態。也就使說,當兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有等待,那麼對線程B調用threadB.interrupt()方法能夠中斷線程B的等待過程。

**interrupt()**方法只能中斷阻塞過程中的線程而不能中斷正在運行過程中的線程。

**unlock:**釋放鎖在finally語句塊中執行。

5.3.3ReadWriteLock中的方法:ReadWriteLock rl= new ReentrantReadWriteLock();**

維護了一對相關的鎖,一個用於只讀操作,另一個用於寫入操作。只要沒有 writer,讀取鎖可以由多個 reader 線程同時保持,而寫入鎖是獨佔的。
rl.readLock();//返回Lock接口可通過Lock接口內方法獲取鎖
rl.writeLock();//返回Lock接口可通過Lock接口內方法獲取鎖

6.volatile

6.1volatile簡介

volatile用以聲明變量的值可能隨時會別的線程修改,使用volatile修飾的變量會強制將修改的值立即寫入主存,主存中值的更新會使緩存中的值失效(非volatile變量不具備這樣的特性,非volatile變量的值會被緩存,線程A更新了這個值,線程B讀取這個變量的值時可能讀到的並不是是線程A更新後的值)。volatile會禁止指令重排。
在這裏插入圖片描述

6.2volatile特性

volatile具有可見性、有序性,不具備原子性。
注意,volatile不具備原子性,這是volatile與java中的synchronized、java.util.concurrent.locks.Lock最大的功能差異,這一點在面試中也是非常容易問到的點。
原子性:原子性通常指多個操作不存在只執行一部分的情況,要麼全部執行要麼全部失敗
可見性:當多個線程訪問同一個變量x時,線程1修改了變量x的值,線程1、線程2…線程n能夠立即讀取到線程1修改後的值。
有序性:即程序執行時按照代碼書寫的先後順序執行。在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性。
在這裏插入圖片描述
那麼可能的一個執行順序是:語句2 -> 語句1 -> 語句3 -> 語句4
那麼可不可能是這個執行順序: 語句2 -> 語句1 -> 語句4 -> 語句3。
不可能,因爲處理器在進行重排序時是會考慮指令之間的數據依賴性,如果一個指令Instruction 2必須用到Instruction 1的結果,那麼處理器會保證Instruction 1會在Instruction 2之前執行。重排序不會影響單個線程內程序執行的結果,但在多線程處理器不能保證

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章