【總結】併發編程

一. 進程和線程

(1)進程:當一個程序運行,從磁盤加載程序代碼至內存,這時就開啓了一個進程
(2)線程:線程就是一個指令流,將指令流中的一條條指令以必定的順序交給CPU執行
(3)併發:(1)從業務上簡單理解就是多個用戶共同競爭一個資源(2)從操做系統層面同一時刻只能有一條指令執行,但多個線程指令被快速的輪換執行。
(4)並行:同一時刻多條指令在多個處理器上同時執行
(5)同步:須要等待結果返回才能繼續執行
(6)異步:不須要等待結果返回就能繼續執行java

二.java線程

1.建立和運行

(1)方法一:繼承thread類,實現run方法(Thread)api

Thread t = new Thread(){
    public void run(){
        //執行的任務
    }
};
t.start();

(2)方法二:實現Runnable接口,實現run方法(Runnable配合Thread)數組

Runnable task = new Runnable(){
    @Overried
    public void run(){
        //要執行的任務
    }
}
Thread t = new Thread(task);
t.start();

(3)方法三:實現Callable接口,用來處理有返回結果的狀況緩存

FutureTask<String> task = new FutureTask<String>(new Callable<String>(){
     public String call() {
         return searcher.search(target);
    }
});
new Thread(task).start();
//獲得結果
Integer result = task.get();

2.查看進程線程

ps -ef 查看全部進程
jps 查看java的進程
kill 進程id 殺死進程
top 動態查看進程
top -h -p 進程id 查看線程信息
jstack 進程id 查看某一時刻進程中全部的線程信息(更詳細,包括線程狀態)安全

3.常見方法

方法名 功能說明
start() 讓線程進入就緒狀態
join() 等待線程結束(線程B中調用A的join方法,等待A執行完畢才繼續執行B)
interrupt() 打斷線程
sleep(long n) 當前線程休眠n毫秒,讓出cpu時間片
yield() 讓出當前線程對cpu使用

4.守護線程

默認狀況下Java進程須要等待全部線程都運行結束纔會結束
但守護線程,其它非守護線程運行結束,即便守護線程正在運行,也會結束性能優化

thread.setDaemon(true)

5.線程狀態該

(1)新建:線程對象建立後進入新建狀態(Thread t = new MyThread();)
(2)就緒:當調用對象的start方法,線程進入就緒狀態,說明此線程已準備好,隨時等待cpu調度執行
(3)運行:cpu調度該線程,進入運行狀態
(4)阻塞:處於運行狀態的線程因爲某種緣由暫時放棄cpu使用權,中止執行,此時進入阻塞狀態
(阻塞分類:①等待阻塞:運行狀態的線程執行了wait方法②同步阻塞:線程獲取sychronized同步鎖失敗③其它阻塞:調用線程的sleep或join或發出I/O請求時)
(5)消亡:線程執行完或因異常退出了run方法,該線程結束生命週期併發

6.synchronized

  1. 臨界區概念

對共享資源進行讀寫操做的代碼塊稱爲臨界區框架

  1. synchronized理解

synchronized其實是用對象鎖保證了臨界區代碼的原子性異步

  1. 三種應用方式

①synchronized修飾普通同步方法,此時鎖的是當前實例的對象
②synchronized修飾靜態同步方法,此時鎖的是類的class對象
③synchronized修飾同步代碼塊,此時鎖的是括號內的對象函數

7.monitor概念

1.對象頭
以32位虛擬機爲例


2.monitor
monitor被翻譯爲監視器或管程,每一個java對象均可以關聯一個monitor對象,若是使用synchronized給對象上鎖後,該對象頭的Mark word就被設置爲指向monitor對象的指針。
monitor中有owner waitset entrylist
(1)當monitor中的owner爲null
(2)當一個線程執行synchronized,會將monitor中全部者設置爲當前線程,monitor只能由一個owner
(3)這時若是另外一個線程也執行synchronized獲取當前鎖對象,就會進入entrylist阻塞
(4)若是線程執行過程當中調用wait方法會進入waitset

8.synchronized完整加鎖流程

1.偏向鎖
偏向鎖會偏向於第一個佔有鎖的線程。若是沒有競爭,已經得到偏向鎖的線程,在未來進入同步塊時不會進行同步操做。
(1)markword中偏向鎖標識設置爲1,鎖標誌位爲01
(2)若是爲可偏向狀態,則檢查線程ID是否指向當前線程,若是是,執行同步代碼塊
(3)若是不是,則經過CAS操做競爭鎖。若是競爭成功,則將Mark Word中線程ID設置爲當前線程。競爭失敗,則說明發生競爭,得到偏向鎖的線程被掛起(撤銷偏向鎖,會致使stop the world),偏向鎖升級爲輕量級鎖

2.輕量級鎖和自旋鎖
(1)虛擬機首先將在當前線程的棧幀中建立一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝
(2)嘗試用cas替換鎖對象的markword,將markword存入鎖記錄 對象指針指向鎖對象,並嘗試用cas替換鎖對象的markword,將markword存入鎖記錄
若是cas成功,對象頭存儲了鎖記錄地址和鎖狀態00,表示由該線程給對象加鎖
若是cas失敗,當前線程嘗試使用自旋來獲取鎖。若是自旋獲取鎖成功,則依然處於輕量級鎖狀態,不然.有兩種狀況:

①若是其它線程已經持有了該對象的輕量級鎖,代表有競爭,進入鎖膨脹狀態  
②若是是本身進行鎖重入,那麼再添加一條鎖記錄做爲重入的計數

3.鎖膨脹(重量級鎖)
(1)爲鎖對象申請monitor鎖,讓鎖對象指向重量級鎖地址
(2)而後進入monitor的entrylist阻塞
(3)當輕量級鎖解鎖時,會失敗。這時根據monitor地址找到monitor對象,喚醒entrylist中阻塞的線程

9.wait&notify

調用wait方法便可進入waitset變爲WATTING狀態
(1)api介紹
obj.wait()讓進入object監視器的線程到waitset等待
obj.notify() 在waitset等待的線程挑一個喚醒
obj.notifyall() 喚醒waitset中全部的線程

(2)sleep和wait的區別
①sleep是Thread方法,而wait是object的方法
②sleep不須要和synchronized配合使用,wait須要和synchronized一塊兒使用
③sleep在睡眠的同時不會釋放對象的鎖,但wait在等待的時候會釋放鎖

(3)交替輸出(ABCABCABC)

class Syncprint {
    private int flag;
    private int loopNumber;

    public Syncprint(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }

    private void print(String str, int waitflag, int nextflag){
        for (int i = 0; i < loopNumber; i++){
            synchronized (this){
                while (this.flag != waitflag){
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                flag = nextflag;
                this.notifyAll();
            }
        }

    }
}
public class Test{
    public static void main(String[] args) {
        Syncprint sync= new Syncprint(1, 5);
        new Thread(() -> {
            sync.print("a", 1, 2);
        }).start();
        new Thread(() -> {
            sync.print("b", 2, 3);
        }).start();
        new Thread(() -> {
            sync.print("c", 3, 1);
        }).start();
    }
}

10.park&unpark

LockSupport.park(); //暫停當前線程
LockSupport.unpark(t1);//開啓t1線程

(1)順序打印

Thread t1 = new Thread(() -> {
    LockSupport.park();
    log.debug("");
},"t1");
Thread t2 = new Thread(() -> {
    log.debug("2");
    LockSupport.unpark(t1);
},"t2");

11.死鎖

t1線程獲取A對象鎖,接下來想獲取B對象的鎖
t2線程獲取B對象鎖,接下來想獲取A對象的鎖

Object A  = new Object();
Object B  = new Object();
Thread t1 = new Thread(() -> {
    synchronized(A){
        sleep(1);
        synchronized(B){
            
        }
    }
},"t1");
Thread t2 = new Thread(() -> {
    synchronized(B){
        sleep(1);
        synchronized(A){
            
        }
    }
},"t2");
ti.start();
t2.start();

12.死鎖的條件

1.互斥條件
A線程得到資源, 其餘線程不能再獲取
2.不可剝奪
A線程未釋放的資源其它線程不能強行奪走
3.請求和保持
請求其它資源,被其它線程佔有.而本身保持的資源不釋放
4.循環等待
存在一種線程資源的循環等待鏈。鏈中每一個線程得到的資源同時被鏈中下一個線程請求

13.怎樣避免死鎖

1.加鎖順序
當多個線程須要相同的一些鎖,按順序加鎖
2.加鎖時限
線程嘗試獲取鎖的時候加上必定的時限,超過期限則放棄對該鎖的請求,並釋放本身佔有的鎖
3.死鎖檢測
當一個線程請求鎖失敗時。這個線程能夠遍歷鎖的關係圖看看是否有死鎖發生(例如,線程A請求鎖7,可是鎖7這個時候被線程B持有,這時線程A就能夠檢查一下線程B是否已經請求了線程A當前所持有的鎖。若是線程B確實有這樣的請求,那麼就是發生了死鎖)

三. 共享模型以內存

1.三條性質

(1)原子性:一個或多個操做,要麼所有執行,要麼所有不執行。
(2)可見性:一個線程對主內存的修改可及時被其它線程觀察到
(3)有序性:一個線程中指令的執行順序(因爲指令重排序,順序通常雜亂)

2.volatile原理(怎麼保證可見性和有序性)

volatile的底層實現原理是內存屏障
對volatile的讀指令前會加入讀屏障
對volatile變量的寫指令後會加入寫屏障

(1)保證可見性
讀屏障保證在該屏障後,對共享變量的讀取,加載的是主內存中最新數據
寫屏障保證在該屏障前,對共享變量的改動,都同步到主存當中

(2)有序性
讀屏障保證指令重排序不會將屏障後的代碼排在屏障前
寫屏障保證指令重排序不會將屏障前的代碼排在屏障後

四.共享模型之無鎖

1.cas

CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。

2.cas怎麼保證拿到的內存中的值是最新的?

volatile修飾變量,能夠保證其可見性

3.原子整數

AutomicBollean
AutomicInteger
AutomicLong

AutomicIntefer i = new AutomicInteger(0);
i.incrementAndGet(); //++i
i.getandincrement();//i++
i.decrementAndGet();//--i

i.getAndAdd(5);//增長5

4.原子引用

AutomicRefence
AutomicMarkableRefence
AutomicStampedRefence

AutomicRefence<BigDecimal> account;

(1).ABA問題
主存中的值被由A改成B再改成A,本地內存再cas時會認爲沒有變過,故更新成功

new AtomicStampedRefence<String> ref = AtomicStampedRefence<>("A",0);

(2).有時候並不關心變量被改了幾回,而是有沒有被改過。就有了AutomicMarkedRefence

new AtomicMarkableRefence<>(bag, true)

5.原子數組

AtomicIntegerArray
AtomicLongArray
AtomicRefenceArray

6.字段更新器

針對某個域進行原子操做(好比引用的屬性,這裏的屬性定義時必須用volatile修飾)
AutomicRefenceFieldUpdater(什麼字段均可以)
AutomicIntegerUpdater(整型字段)
AutomicLongUpdater(long字段)

AutomicRefenceFieldUpdater updater = AutomicRefenceFieldUpdater.newUpdater(student.class, String.class, "name");
updater.compareAndSet(stu,null,"張三")

7.原子累加器

IntegerAdder
LongAdder
提供比原子整數更快的累加效果
原理:
有競爭時,設置多個累加單元,最後將各個累加單元再彙總,減小cas失敗的次數

new LongAdder();
adder.increment();

五.線程池

1.線程池狀態

ThreadPoolExecutor使用int的高3位來表示線程池狀態,低29位表示線程數量


目的是將線程狀態和線程個數合二爲一,這樣能夠用一次cas原子操做進行賦值

2.構造方法

①corePoolSize:核心線程池的大小,若是核心線程池有空閒位置,新的任務就會被核心線程池新建一個線程執行,執行完畢後不會銷燬線程,線程會進入緩存隊列等待再次被運行。

④workQueue:緩存隊列,用來存放等待被執行的任務。

②maximunPoolSize:線程池能建立最大的線程數量。若是核心線程池和緩存隊列都已經滿了,新的任務進來就會建立救急線程來執行。可是數量不能超過maximunPoolSize,否側會採起拒絕接受任務策略,咱們下面會具體分析。

③keepAliveTime:救急線程線程可以空閒的最長時間,超過期間,線程終止。這個參數默認只有在線程數量超過核心線程池大小時纔會起做用。只要線程數量不超過核心線程大小,就不會起做用。

⑤threadFactory:線程工廠,用來建立線程

⑥handler:拒絕策略


abortPolicy:拋出異常(默認)
discardPolicy:放棄本次任務
discardoldestPolicy:放棄隊列中最先的任務,本任務取代
callerrunPolicy:讓調用者運行任務

3.實現方式

(1)newFixedThreadPool
核心線程數=最大線程數(沒有救急線程,所以也無需超時時間)
阻塞隊列是無界的,能夠聽任意數量任務
(2)newCachedThreadPool
核心線程數爲0,最大線程數是Integer.MAX_VALUE,救急線程存貨時間60s
隊列採用SynchronousQueue。它沒有容量,沒有線程取是放不進去的
(3)newSingleThreadExecutor
線程固定爲1,其它任務來時放進無界隊列等待

4.提交任務

//執行任務
void execute(Runnable command);
//提交任務(有返回值)
submit(Callable task);
//提交任務隊列全部任務
invokeAll()
//提交任務隊列全部任務,哪一個先執行完畢,返回結果,其它任務取消  
invokeAny();

5.線程池原理

若是當前線程池中正在執行的線程數目小於corePoolSize,則每來一個任務,就會建立一個線程去執行這個任務;

若是當前線程池中正在執行任務的的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(通常來講是任務緩存隊列已滿),則會嘗試建立新的線程去執行這個任務; ;

若是線程池中的線程數量大於 corePoolSize時,若是某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;

若是當前線程池中的線程數目達到maximumPoolSize,則會採起任務拒絕策略進行處理

六.J.U.C

1.asq原理

abstractQueuedSynchronizer,是阻塞式鎖和相關同步工具的框架
1.特色:
(1)用state表示資源狀態(獨佔和共享),子類須要定義如何維護這個狀態,控制如何獲取和釋放鎖
(2)提供基於fifo的等待隊列,相似與monitor的entrylist
(3)條件變量來實現等待喚醒,支持多種條件變量,相似monitor的waitset

2.ReentrantLock

3.StampedLock

jdk1.8加入,是爲了進一步優化讀性能,它的特色是在使用讀鎖,寫鎖時都必須配合戳使用
怎樣實現性能優化?stampedlock支持樂觀讀取完畢後進行一次戳校驗,代表這段時間沒有寫操做,能夠安全使用,若是沒有經過,才從新獲取讀鎖保證數據安全。

4.Semaphore

信號量,用來限制同時訪問共享資源的線程上限

5.CountDownLatch

用來進行線程同步協做,等待全部線程完成倒計時
構造函數初始化等待計數值,await()等待計數歸零,countDown()計數減1

6.線程安全集合類


1.concurrent的弱一致性
(1)遍歷時的弱一致性:當利用迭代器遍歷時,若是容器發生修改,迭代器仍然能夠進行遍歷,這時內容是舊的
(2)求大小弱一致性:size操做未必是100%準確

2.hashmap爲何線程不安全? (1).在JDK1.7中,當併發執行擴容操做時會形成環形鏈和數據丟失的狀況。 (2).在JDK1.8中,在併發執行put操做時會發生數據覆蓋的狀況。

相關文章
相關標籤/搜索