(1)進程:當一個程序運行,從磁盤加載程序代碼至內存,這時就開啓了一個進程
(2)線程:線程就是一個指令流,將指令流中的一條條指令以必定的順序交給CPU執行
(3)併發:(1)從業務上簡單理解就是多個用戶共同競爭一個資源(2)從操做系統層面同一時刻只能有一條指令執行,但多個線程指令被快速的輪換執行。
(4)並行:同一時刻多條指令在多個處理器上同時執行
(5)同步:須要等待結果返回才能繼續執行
(6)異步:不須要等待結果返回就能繼續執行java
(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();
ps -ef 查看全部進程
jps 查看java的進程
kill 進程id 殺死進程
top 動態查看進程
top -h -p 進程id 查看線程信息
jstack 進程id 查看某一時刻進程中全部的線程信息(更詳細,包括線程狀態)安全
方法名 | 功能說明 |
---|---|
start() | 讓線程進入就緒狀態 |
join() | 等待線程結束(線程B中調用A的join方法,等待A執行完畢才繼續執行B) |
interrupt() | 打斷線程 |
sleep(long n) | 當前線程休眠n毫秒,讓出cpu時間片 |
yield() | 讓出當前線程對cpu使用 |
默認狀況下Java進程須要等待全部線程都運行結束纔會結束
但守護線程,其它非守護線程運行結束,即便守護線程正在運行,也會結束性能優化
thread.setDaemon(true)
(1)新建:線程對象建立後進入新建狀態(Thread t = new MyThread();)
(2)就緒:當調用對象的start方法,線程進入就緒狀態,說明此線程已準備好,隨時等待cpu調度執行
(3)運行:cpu調度該線程,進入運行狀態
(4)阻塞:處於運行狀態的線程因爲某種緣由暫時放棄cpu使用權,中止執行,此時進入阻塞狀態
(阻塞分類:①等待阻塞:運行狀態的線程執行了wait方法②同步阻塞:線程獲取sychronized同步鎖失敗③其它阻塞:調用線程的sleep或join或發出I/O請求時)
(5)消亡:線程執行完或因異常退出了run方法,該線程結束生命週期併發
對共享資源進行讀寫操做的代碼塊稱爲臨界區框架
synchronized其實是用對象鎖保證了臨界區代碼的原子性異步
①synchronized修飾普通同步方法,此時鎖的是當前實例的對象
②synchronized修飾靜態同步方法,此時鎖的是類的class對象
③synchronized修飾同步代碼塊,此時鎖的是括號內的對象函數
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
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中阻塞的線程
調用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(); } }
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");
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();
1.互斥條件
A線程得到資源, 其餘線程不能再獲取
2.不可剝奪
A線程未釋放的資源其它線程不能強行奪走
3.請求和保持
請求其它資源,被其它線程佔有.而本身保持的資源不釋放
4.循環等待
存在一種線程資源的循環等待鏈。鏈中每一個線程得到的資源同時被鏈中下一個線程請求
1.加鎖順序
當多個線程須要相同的一些鎖,按順序加鎖
2.加鎖時限
線程嘗試獲取鎖的時候加上必定的時限,超過期限則放棄對該鎖的請求,並釋放本身佔有的鎖
3.死鎖檢測
當一個線程請求鎖失敗時。這個線程能夠遍歷鎖的關係圖看看是否有死鎖發生(例如,線程A請求鎖7,可是鎖7這個時候被線程B持有,這時線程A就能夠檢查一下線程B是否已經請求了線程A當前所持有的鎖。若是線程B確實有這樣的請求,那麼就是發生了死鎖)
(1)原子性:一個或多個操做,要麼所有執行,要麼所有不執行。
(2)可見性:一個線程對主內存的修改可及時被其它線程觀察到
(3)有序性:一個線程中指令的執行順序(因爲指令重排序,順序通常雜亂)
volatile的底層實現原理是內存屏障
對volatile的讀指令前會加入讀屏障
對volatile變量的寫指令後會加入寫屏障
(1)保證可見性
讀屏障保證在該屏障後,對共享變量的讀取,加載的是主內存中最新數據
寫屏障保證在該屏障前,對共享變量的改動,都同步到主存當中
(2)有序性
讀屏障保證指令重排序不會將屏障後的代碼排在屏障前
寫屏障保證指令重排序不會將屏障前的代碼排在屏障後
CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。
volatile修飾變量,能夠保證其可見性
AutomicBollean
AutomicInteger
AutomicLong
AutomicIntefer i = new AutomicInteger(0); i.incrementAndGet(); //++i i.getandincrement();//i++ i.decrementAndGet();//--i i.getAndAdd(5);//增長5
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)
AtomicIntegerArray
AtomicLongArray
AtomicRefenceArray
針對某個域進行原子操做(好比引用的屬性,這裏的屬性定義時必須用volatile修飾)
AutomicRefenceFieldUpdater(什麼字段均可以)
AutomicIntegerUpdater(整型字段)
AutomicLongUpdater(long字段)
AutomicRefenceFieldUpdater updater = AutomicRefenceFieldUpdater.newUpdater(student.class, String.class, "name"); updater.compareAndSet(stu,null,"張三")
IntegerAdder
LongAdder
提供比原子整數更快的累加效果
原理:
有競爭時,設置多個累加單元,最後將各個累加單元再彙總,減小cas失敗的次數
new LongAdder(); adder.increment();
ThreadPoolExecutor使用int的高3位來表示線程池狀態,低29位表示線程數量
目的是將線程狀態和線程個數合二爲一,這樣能夠用一次cas原子操做進行賦值
①corePoolSize:核心線程池的大小,若是核心線程池有空閒位置,新的任務就會被核心線程池新建一個線程執行,執行完畢後不會銷燬線程,線程會進入緩存隊列等待再次被運行。
④workQueue:緩存隊列,用來存放等待被執行的任務。
②maximunPoolSize:線程池能建立最大的線程數量。若是核心線程池和緩存隊列都已經滿了,新的任務進來就會建立救急線程來執行。可是數量不能超過maximunPoolSize,否側會採起拒絕接受任務策略,咱們下面會具體分析。
③keepAliveTime:救急線程線程可以空閒的最長時間,超過期間,線程終止。這個參數默認只有在線程數量超過核心線程池大小時纔會起做用。只要線程數量不超過核心線程大小,就不會起做用。
⑤threadFactory:線程工廠,用來建立線程
⑥handler:拒絕策略
abortPolicy:拋出異常(默認)
discardPolicy:放棄本次任務
discardoldestPolicy:放棄隊列中最先的任務,本任務取代
callerrunPolicy:讓調用者運行任務
(1)newFixedThreadPool
核心線程數=最大線程數(沒有救急線程,所以也無需超時時間)
阻塞隊列是無界的,能夠聽任意數量任務
(2)newCachedThreadPool
核心線程數爲0,最大線程數是Integer.MAX_VALUE,救急線程存貨時間60s
隊列採用SynchronousQueue。它沒有容量,沒有線程取是放不進去的
(3)newSingleThreadExecutor
線程固定爲1,其它任務來時放進無界隊列等待
//執行任務 void execute(Runnable command); //提交任務(有返回值) submit(Callable task); //提交任務隊列全部任務 invokeAll() //提交任務隊列全部任務,哪一個先執行完畢,返回結果,其它任務取消 invokeAny();
若是當前線程池中正在執行的線程數目小於corePoolSize,則每來一個任務,就會建立一個線程去執行這個任務;
若是當前線程池中正在執行任務的的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(通常來講是任務緩存隊列已滿),則會嘗試建立新的線程去執行這個任務; ;
若是線程池中的線程數量大於 corePoolSize時,若是某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;
若是當前線程池中的線程數目達到maximumPoolSize,則會採起任務拒絕策略進行處理
abstractQueuedSynchronizer,是阻塞式鎖和相關同步工具的框架
1.特色:
(1)用state表示資源狀態(獨佔和共享),子類須要定義如何維護這個狀態,控制如何獲取和釋放鎖
(2)提供基於fifo的等待隊列,相似與monitor的entrylist
(3)條件變量來實現等待喚醒,支持多種條件變量,相似monitor的waitset
jdk1.8加入,是爲了進一步優化讀性能,它的特色是在使用讀鎖,寫鎖時都必須配合戳使用
怎樣實現性能優化?stampedlock支持樂觀讀取完畢後進行一次戳校驗,代表這段時間沒有寫操做,能夠安全使用,若是沒有經過,才從新獲取讀鎖保證數據安全。
信號量,用來限制同時訪問共享資源的線程上限
用來進行線程同步協做,等待全部線程完成倒計時
構造函數初始化等待計數值,await()等待計數歸零,countDown()計數減1
1.concurrent的弱一致性
(1)遍歷時的弱一致性:當利用迭代器遍歷時,若是容器發生修改,迭代器仍然能夠進行遍歷,這時內容是舊的
(2)求大小弱一致性:size操做未必是100%準確
2.hashmap爲何線程不安全? (1).在JDK1.7中,當併發執行擴容操做時會形成環形鏈和數據丟失的狀況。 (2).在JDK1.8中,在併發執行put操做時會發生數據覆蓋的狀況。