java 多線程學習總結

1、多線程基礎
1.進程和線程的區別?
1).什麼是進程?
進程是正在運行的應用程序,進程是線程的集合。
2).什麼是線程?
線程是一條執行路徑,一個獨立的執行單元。
2.爲何使用多線程?
提升應用程序效率
3.多線程的應用場景?
多線程下載、QQ、前端開發ajax(異步上傳)、分佈式job(須要同一時間多個任務調度)
4.建立多線程有幾種方式?
1.繼承Tread類
2.實現Runnable接口
3.使用匿名內部類建立
4.callable
5.使用線程池建立
5.守護線程和非守護線程的區別?
守護線程:和主線程一塊兒銷燬
非守護線程: 和主線程互不影響
6.多線程有幾種狀態?
1.新建狀態
2.就緒狀態
3.運行狀態
4.阻塞狀態
5.死亡狀態
2、線程安全問題
1.什麼是線程安全問題?
當多個線程共享同一個全局變量,作寫的時候可能會受到其餘線程的干擾,致使數據問題,這種現象叫作線程安全問題。(作讀的時候不會產生線程安全問題)。
2.當多個線程共享同一個局部變量,作寫操做的時候會產生線程安全問題嗎?爲何?
不會,由於
3.線程如何實現同步?有哪些解決辦法?
多個線程共享同一個全局變量,數據安全問題,保證數據的原子性
解決辦法:
1.synchronized ----自動鎖
2.lock ----jdk1.5併發包 ---手動鎖
4.同步須要有一些條件
1.必須有兩個線程以上
2.多個線程想要同步,必須用同一把鎖
3.保證只有一個線程進行執行
5.同步的原理
1.有一個線程已經拿到了鎖,其餘線程已經有CPU執行,一直在排隊,等待其餘線程釋放鎖。
2.鎖何時釋放?
代碼執行完畢或者程序拋出異常都會被釋放掉
3.鎖已經釋放掉,其餘線程開始獲取鎖進入同步中去
4.鎖的資源競爭
5.死鎖問題
6.同步的缺點
效率低
7.什麼是同步函數?
在方法上修飾synchronized稱爲同步函數
8.什麼是靜態同步函數?
在方法上加上static關鍵字,使用synchronized 關鍵字修飾 或者使用類.class文件。靜態的同步函數使用的鎖是該函數所屬字節碼文件對象
9.兩個線程,一個線程使用同步函數,另外一個線程使用靜態同步函數能實現同步嗎?
不能,同步函數使用this鎖,靜態同步函數使用當前字節碼文件
10.加鎖有什麼好處?
加鎖是爲了保證同步,同步是保證數據安全問題和原子問題和分佈式鎖、高併發和JVM是沒有關係的。
11.總結
synchronized 修飾方法使用鎖是當前this鎖。
synchronized 修飾靜態方法使用鎖是當前類的字節碼文件
3、線程死鎖
1.什麼是多線程死鎖?
同步中嵌套同步,致使鎖沒法釋放,一直等待,變爲死鎖。
4、Java內存模型
1.什麼是java內存模型?
java內存模型簡稱jmm,定義了一個線程對另外一個線程可見。共享變量存放在主內存中,每一個線程都有本身的本地內存,當多個線程同時訪問一個數據的時候,可能本地內存沒有及時刷新到主內存,因此就會發生線程安全問題。
2.多線程有哪些特性?
原子性、可見性、有序性
3.什麼是原子性?
即一個操做或者多個操做要麼所有執行而且執行的過程不會被任何因素打斷,要麼就都不執行。
4.什麼是可見性
當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其餘線程可以當即看獲得修改的值。
5.什麼是有序性
程序執行的順序按照代碼的前後順序執行。通常來講處理器爲了提升程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行前後順序同代碼中的順序一致,可是它會保證程序最終執行結果和代碼順序執行的結果是一致的。
5、Volatile
1.什麼是Volatile?
Volatile 關鍵字的做用是變量在多個線程之間可見。
2.已經將結果設置爲fasle爲何還一直在運行呢?
線程之間是不可見的,讀取的是副本,沒有及時讀取到主內存結果。解決辦法使用Volatile關鍵字將解決線程之間可見性, 強制線程每次讀取該值的時候都去「主內存」中取值
3.Volatile非原子性
Volatile不具有數據原子性
4.volatile與synchronized區別?
僅靠volatile不能保證線程的安全性(原子性)。
1.volatile輕量級,只能修飾變量。synchronized重量級,還可修飾方法
2.volatile只能保證數據的可見性,不能用來同步,由於多個線程併發訪問volatile修飾的變量不會阻塞
3.synchronized不只保證可見性,並且還保證原子性,由於,只有得到了鎖的線程才能進入臨界區,從而保證臨界區中的全部語句都所有執行。多個線程爭搶synchronized鎖對象時,會出現阻塞。
4.線程安全性
線程安全性包括兩個方面,1.可見性。2.原子性。
僅僅使用volatile並不能保證線程安全性。而synchronized則可實現線程的安全性。
6、多線程之間實現通信
1.什麼是多線程之間通信?
多個線程之間通信是多個線程在操做同一個資源,可是操做的動做不一樣。
2.wait和notify的區別?
wait的做用:讓當前線程從運行狀態變爲休眠狀態,釋放鎖的資源。
notify的做用:讓當前線程從休眠狀態變爲運行狀態。
這兩個方法只能在同步中使用。
3.wait和sleep的區別?
sleep方法屬於Thread類中的,wait是object類中的,sleep方法致使了程序暫停執行指定的時間,讓出了CPU該其餘線程,可是他的監控狀態依然保持着,當指定時間到了又會自動恢復運行狀態。
在調用sleep方法的過程當中,線程不會釋放對象鎖。
當調用wait方法的時候,線程會釋放對象鎖,進入等待此對象的等待線程池,只有針對此對象調用notify方法後本線程才進入對象線程池準備,獲取對象鎖進入運行狀態。
7、JDK1.5-Lock鎖
1.Lcok鎖從何時開始上鎖,何時釋放鎖?
手動上鎖,手動釋放鎖,靈活性高。
2.多線程併發和網站併發有什麼區別?
多線程併發是操做同一個資源,網站併發是多個請求同時訪問一臺服務。
3.Lock 接口與 synchronized 關鍵字的區別?
Lock 接口能夠嘗試非阻塞地獲取鎖 當前線程嘗試獲取鎖。若是這一時刻鎖沒有被其餘線程獲取到,則成功獲取並持有鎖。
Lock 接口能被中斷地獲取鎖 與 synchronized 不一樣,獲取到鎖的線程可以響應中斷,當獲取到的鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放。
Lock 接口在指定的截止時間以前獲取鎖,若是截止時間到了依舊沒法獲取鎖,則返回。
4.Condition用法
Condition的功能相似於在傳統的線程技術中的,Object.wait()和Object.notify()的功能。
5.怎麼中止線程?
8、ThreadLocal
1.什麼是ThreadLocal?
ThreadLocal提升一個線程的局部變量,訪問某個線程擁有本身局部變量。當使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。
2.ThreadLocal的接口方法
1.void set(Object value):設置當前線程的線程局部變量的值。
2.public Object get():該方法返回當前線程所對應的線程局部變量。
3.public void remove():將當前線程局部變量的值刪除,目的是爲了減小內存的佔用,該方法是JDK 5.0新增的方法。須要指出的是,當線程結束後,對應該線程的局部變量將自動被垃圾回收,因此顯式調用該方法清除線程的局部變量並非必須的操做,但它能夠加快內存回收的速度。
4.protected Object initialValue():返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是爲了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,而且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。
3.ThreadLocal實現原理
ThreadLocal經過map集合實現
9、java併發包
1.vector和arrayList的區別?
實現原理:都是經過數組實現,查詢速度快,增長、修改、刪除速度慢
區別:線程安全問題,vector是線程安全的,ArrayList是線程不安全的,ArrayList效率高。
2.HasTable與HasMap的區別?
1.hashtable是線程安全的,hashMap是線程不安全的。
HastMap是一個接口 是map接口的子接口,是將鍵映射到值的對象,其中鍵和值都是對象,而且不能包含重複鍵,但能夠包含重複值。HashMap容許null key和null value,而hashtable不容許。
實現原理:經過鏈表和數組實現,put()方法經過hashcode取模獲得下標位置,一致性取模算法。
2.HashTable是線程安全的一個Collection。
3.HashMap是Hashtable的輕量級實現(非線程安全的實現),他們都完成了Map接口,主要區別在於HashMap容許空(null)鍵值(key),因爲非線程安全,效率上可能高於Hashtable。
4.HashMap容許將null做爲一個entry的key或者value,而Hashtable不容許。
HashMap把Hashtable的contains方法去掉了,改爲containsvalue和containsKey。
3.怎麼證實hashtable是線程安全的?
查看源碼的put方法
4.synchronizedMap
Collections.synchronized*(m) 將線程不安全額集合變爲線程安全集合
5.ConcurrentHashMap------併發包
實現原理:將一個總體拆分紅多個小的hashtable,默認分紅16段。
1.ConcurrentMap接口下有倆個重要的實現
1.ConcurrentHashMap
2.ConcurrentskipListMap (支持併發排序功能。彌補ConcurrentHashMap)
2.ConcurrentHashMap內部使用段(Segment)來表示這些不一樣的部分,每一個段其實就是一個小的HashTable,它們有本身的鎖。只要多個修改操做發生在不一樣的段上,它們就能夠併發進行。把一個總體分紅了16個段(Segment.也就是最高支持16個線程的併發修改操做。
這也是在重線程場景時減少鎖的粒度從而下降鎖競爭的一種方案。而且代碼中大多共享變量使用volatile關鍵字聲明,目的是第一時間獲取修改的內容,性能很是好。
6.CountDownLatch
CountDownLatch類位於java.util.concurrent包下,利用它能夠實現相似計數器的功能。
7.CyclicBarrier
CyclicBarrier初始化時規定一個數目,而後計算調用了CyclicBarrier.await()進入等待的線程數。當線程數達到了這個數目時,全部進入等待狀態的線程被喚醒並繼續。
8.Semaphore
是一種基於計數的信號量。它能夠設定一個閾值,基於此,多個線程競爭獲取許可信號,作本身的申請後歸還,超過閾值後,線程申請許可信號將會被阻塞。Semaphore能夠用來構建一些對象池,資源池之類的,好比數據庫鏈接池,咱們也能夠建立計數爲1的Semaphore,將其做爲一種相似互斥鎖的機制,這也叫二元信號量,表示兩種互斥狀態。
10、併發隊列
在併發隊列上JDK提供了兩套實現,一個是以ConcurrentLinkedQueue爲表明的高性能隊列,一個是以BlockingQueue接口爲表明的阻塞隊列,不管哪一種都繼承自Queue。
1.ConcurrentLinkedDeque
是一個適用於高併發場景下的隊列,經過無鎖的方式,實現了高併發狀態下的高性能,一般ConcurrentLinkedQueue性能好於BlockingQueue.它是一個基於連接節點的無界線程安全隊列。該隊列的元素遵循先進先出的原則。頭是最早加入的,尾是最近加入的,該隊列不容許null元素。
2.ConcurrentLinkedQueue重要方法
add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中這倆個方法沒有任何區別)
poll() 和peek() 都是取頭元素節點,區別在於前者會刪除元素,後者不會。
3.BlockingQueue
阻塞隊列(BlockingQueue)是一個支持兩個附加操做的隊列。這兩個附加的操做是:在隊列爲空時,獲取元素的線程會等待隊列變爲非空。當隊列滿時,存儲元素的線程會等待隊列可用。
阻塞隊列經常使用於生產者和消費者的場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器裏拿元素。
4.ArrayBlockingQueue
ArrayBlockingQueue是一個有邊界的阻塞隊列,它的內部實現是一個數組。有邊界的意思是它的容量是有限的,咱們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變。
ArrayBlockingQueue是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。
5.LinkedBlockingQueue
LinkedBlockingQueue阻塞隊列大小的配置是可選的,若是咱們初始化時指定一個大小,它就是有邊界的,若是不指定,它就是無邊界的。說是無邊界,實際上是採用了默認大小爲Integer.MAX_VALUE的容量 。它的內部實現是一個鏈表。
和ArrayBlockingQueue同樣,LinkedBlockingQueue 也是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。
6.PriorityBlockingQueue
PriorityBlockingQueue是一個沒有邊界的隊列,它的排序規則和 java.util.PriorityQueue同樣。PriorityBlockingQueue中容許插入null對象。
全部插入PriorityBlockingQueue的對象必須實現 java.lang.Comparable接口,隊列優先級的排序規則就是按照咱們對這個接口的實現來定義的。
能夠從PriorityBlockingQueue得到一個迭代器Iterator,但這個迭代器並不保證按照優先級順序進行迭代。
7.SynchronousQueue
SynchronousQueue隊列內部僅容許容納一個元素。當一個線程插入一個元素後會被阻塞,除非這個元素被另外一個線程消費。
11、線程池
1.什麼是線程池?
java中的線程池是運用場景最多的併發框架,幾乎全部須要異步或併發執行任務的程序均可以使用線程池。
好處:1.下降資源消耗。經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。
2.提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能當即執行。
3.提升線程的可管理性。線程是稀缺資源,若是無限制地建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一分配、調優和監控。可是,要作到合理利用線程池,必須對其實現原理了如指掌。
2.線程池的做用
程池是爲忽然大量爆發的線程設計的,經過有限的幾個固定線程爲大量的操做服務,減小了建立和銷燬線程所需的時間,從而提升效率。
若是一個線程的時間很是長,就不必用線程池了(不是不能做長時間操做,而是不宜。),何況咱們還不能控制線程池中線程的開始、掛起、和停止。
3.線程池的分類
1.ThreadPoolExecutor
java使用線程核心走的ThreadPoolExecutor
2.ThreadPoolExecutor構造函數參數
1.corePoolSize:核心池的大小。當有任務來以後,就會建立一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中。
2.maximumPoolSize:線程池最大線程數,它表示在線程池中最多能建立多少個線程
3.keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。
4.unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性
4.線程池的建立方式(Executors封裝好的)
1.newCachedThreadPool:建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則新建線程。
2.newFixedThreadPool:建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
3.newScheduledThreadPool:建立一個定長線程池,支持定時及週期性任務執行。
4.newSingleThreadExecutor:建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行。
5.newCachedThreadPool
建立可緩存線程池,線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。
6.newFixedThreadPool
建立一個定長線程池,可控制線程最大併發數,由於線程池大小爲3,每一個任務輸出index後sleep 2秒,因此每兩秒打印3個數字。定長線程池的大小最好根據系統資源進行設置。
7.newScheduledThreadPool
建立一個定長線程池,支持定時及週期性任務執行。
8.newSingleThreadExecutor
建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行。
12、線程池原理剖析
提交一個任務到線程池中,線程池的處理流程:
1.判斷線程池裏的核心線程是否都在執行任務,若是不是(核心線程空閒或者還有核心線程沒有被建立)則建立一個新的工做線程來執行任務。若是核心線程都在執行任務,則進入下個流程。
2.線程池判斷工做隊列是否已滿,若是工做隊列沒有滿,則將新提交的任務存儲在這個工做隊列裏。若是工做隊列滿了,則進入下個流程。
3.判斷線程池裏的線程是否都處於工做狀態,若是沒有,則建立一個新的工做線程來執行任務。若是已經滿了,則交給飽和策略來處理這個任務。
十3、合理配置線程池
要想合理的配置線程池,就必須首先分析任務特性,能夠從如下幾個角度來進行分析:
1.任務的性質:CPU密集型任務,IO密集型任務和混合型任務。
2.任務的優先級:高,中和低。
3.任務的執行時間:長,中和短。
4.任務的依賴性:是否依賴其餘系統資源,如數據庫鏈接。
十4、Java鎖的深度化
1.悲觀鎖、樂觀鎖、排他鎖
1.悲觀鎖:悲觀鎖悲觀的認爲每一次操做都會形成更新丟失問題,在每次查詢時加上排他鎖。
每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖。
2.樂觀鎖:樂觀鎖會樂觀的認爲每次查詢都不會形成更新丟失,利用版本字段控制
2.重入鎖
鎖做爲併發共享數據,保證一致性的工具,在JAVA平臺有多種實現(如 synchronized 和 ReentrantLock等等 ) 。這些已經寫好提供的鎖爲咱們開發提供了便利。
重入鎖,也叫作遞歸鎖,指的是同一線程 外層函數得到鎖以後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。
在JAVA環境下 ReentrantLock 和synchronized 都是 可重入鎖
3.讀寫鎖
相比Java中的鎖(Locks in Java)裏Lock實現,讀寫鎖更復雜一些。假設你的程序中涉及到對一些共享資源的讀和寫操做,且寫操做沒有讀操做那麼頻繁。
在沒有寫操做的時候,兩個線程同時讀一個資源沒有任何問題,因此應該容許多個線程能在同時讀取共享資源。可是若是有一個線程想去寫這些共享資源,就不該該再有其它線程對該資源進行讀或寫
(譯者注:也就是說:讀-讀能共存,讀-寫不能共存,寫-寫不能共存)。這就須要一個讀/寫鎖來解決這個問題。Java5在java.util.concurrent包中已經包含了讀寫鎖。儘管如此,咱們仍是應該瞭解其實現背後的原理。
4.CAS無鎖機制
1.與鎖相比,使用比較交換(下文簡稱CAS)會使程序看起來更加複雜一些。但因爲其非阻塞性,它對死鎖問題天生免疫,而且,線程間的相互影響也遠遠比基於鎖的方式要小。更爲重要的是,使用無鎖的方式徹底沒有鎖競爭帶來的系統開銷,也沒有線程間頻繁調度帶來的開銷,所以,它要比基於鎖的方式擁有更優越的性能。
2.無鎖的好處:第一,在高併發的狀況下,它比有鎖的程序擁有更好的性能;第二,它天生就是死鎖免疫的。就憑藉這兩個優點,就值得咱們冒險嘗試使用無鎖的併發。
3.CAS算法的過程是這樣:它包含三個參數CAS(V,E,N): V表示要更新的變量,E表示預期值,N表示新值。僅當V值等於E值時,纔會將V的值設爲N,若是V值和E值不一樣,則說明已經有其餘線程作了更新,則當前線程什麼都不作。最後,CAS返回當前V的真實值。
4.CAS操做是抱着樂觀的態度進行的,它老是認爲本身能夠成功完成操做。當多個線程同時使用CAS操做一個變量時,只有一個會勝出,併成功更新,其他均會失敗。失敗的線程不會被掛起,僅是被告知失敗,而且容許再次嘗試,固然也容許失敗的線程放棄操做。基於這樣的原理,CAS操做即便沒有鎖,也能夠發現其餘線程對當前線程的干擾,並進行恰當的處理。
5.簡單地說,CAS須要你額外給出一個指望值,也就是你認爲這個變量如今應該是什麼樣子的。若是變量不是你想象的那樣,那說明它已經被別人修改過了。你就從新讀取,再次嘗試修改就行了。
6.在硬件層面,大部分的現代處理器都已經支持原子化的CAS指令。在JDK 5.0之後,虛擬機即可以使用這個指令來實現併發操做和併發數據結構,而且,這種操做在虛擬機中能夠說是無處不在。
5.自旋鎖
自旋鎖是採用讓當前線程不停地的在循環體內執行實現的,當循環的條件被其餘線程改變時 才能進入臨界區。
當一個線程 調用這個不可重入的自旋鎖去加鎖的時候沒問題,當再次調用lock()的時候,由於自旋鎖的持有引用已經不爲空了,該線程對象會誤認爲是別人的線程持有了自旋鎖,使用了CAS原子操做,lock函數將owner設置爲當前線程,而且預測原來的值爲空。unlock函數將owner設置爲null,而且預測值爲當前線程。
當有第二個線程調用lock操做時因爲owner值不爲空,致使循環一直被執行,直至第一個線程調用unlock函數將owner設置爲null,第二個線程才能進入臨界區。
因爲自旋鎖只是將當前線程不停地執行循環體,不進行線程狀態的改變,因此響應速度更快。但當線程數不停增長時,性能降低明顯,由於每一個線程都須要執行,佔用CPU時間。若是線程競爭不激烈,而且保持鎖的時間段。適合使用自旋鎖。
6.分佈式鎖
若是想在不一樣的jvm中保證數據同步,使用分佈式鎖技術。有數據庫實現、緩存實現、Zookeeper分佈式鎖前端

7.悲觀鎖與樂觀鎖的區別?
悲觀鎖與樂觀鎖若是查詢量小可使用樂觀鎖,樂觀鎖使用版本控制操做。java

相關文章
相關標籤/搜索