摘要:創建線程的方式方式一將類聲明為的子類。將該線程標記為守護線程或用戶線程。其中方法隱含的線程為父線程。恢復線程,已過時。等待該線程銷毀終止。更多的使當前線程在鎖存器倒計數至零之前一直等待,除非線
知識體系圖:
1、線程是什么?線程是進程中獨立運行的子任務。
2、創建線程的方式方式一:將類聲明為 Thread 的子類。該子類應重寫 Thread 類的 run 方法
方式二:聲明實現 Runnable 接口的類。該類然后實現 run 方法
推薦方式二,因為接口方式比繼承方式更靈活,也減少程序間的耦合。
3、獲取當前線程信息?Thread.currentThread()
4、線程的分類線程分為守護線程、用戶線程。線程初始化默認為用戶線程。
setDaemon(true) 將該線程標記為守護線程或用戶線程。
特性:設置守護線程,會作為進程的守護者,如果進程內沒有其他非守護線程,那么守護線程也會被銷毀,即使可能線程內沒有運行結束。
5、線程間的關系?某線程a 中啟動另外一個線程 t,那么我們稱 線程 t是 線程a 的一個子線程,而 線程a 是 線程t 的 父線程。
最典型的就是我們在main方法中 啟動 一個 線程去執行。其中main方法隱含的main線程為父線程。
6、線程API一覽:如何啟動、停止、暫停、恢復線程?(1)start() 使線程處于就緒狀態,Java虛擬機會調用該線程的run方法;
(2)stop() 停止線程,已過時,存在不安全性:
一是可能請理性的工作得不得完成;
二是可能對鎖定的對象進行“解鎖”,導致數據不同步不一致的情況。
推薦 使用 interrupt() +拋異常 中斷線程。
(3)suspend() 暫停線程,已過時。
resume() 恢復線程,已過時。
suspend 與resume 不建議使用,存在缺陷:
一是可能獨占同步對象;
二是導致數據不一致。
(4)yield() 放棄當前線程的CPU資源。放棄時間不確認,也有可能剛剛放棄又獲得CPU資源。
(5)t.join() 等待該線程t 銷毀終止。
7、synchronized關鍵字用法一 原子性(互斥性):實現多線程的同步機制,使得鎖內代碼的運行必需先獲得對應的鎖,運行完后自動釋放對應的鎖。
二 內存可見性:在同一鎖情況下,synchronized鎖內代碼保證變量的可見性。
三 可重入性:當一個線程獲取一個對象的鎖,再次請求該對象的鎖時是可以再次獲取該對象的鎖的。
如果在synchronized鎖內發生異常,鎖會被釋放。
總結:
(1)synchronized方法 與 synchronized(this) 代碼塊 鎖定的都是當前對象,不同的只是同步代碼的范圍
(2)synchronized (非this對象x) 將對象x本身作為“對象監視器”:
a、多個線程同時執行 synchronized(x) 代碼塊,呈現同步效果。
b、當其他線程同時執行對象x里面的 synchronized方法時,呈現同步效果。
c、當其他線程同時執行對象x里面的 synchronized(this)方法時,呈現同步效果。
(3)靜態synchronized方法 與 synchronized(calss)代碼塊 鎖定的都是Class鎖。Class 鎖與 對象鎖 不是同一個鎖,兩者同時使用情況可能呈異步效果。
(4)盡量不使用 synchronized(string),是因為string的實際鎖為string的常量池對象,多個值相同的string對象可能持有同一個鎖。
8、volatile關鍵字用法一 內存可見性:保證變量的可見性,線程在每次使用變量的時候,都會讀取變量修改后的最的值。
二 不保證原子性。
9、線程間的通信方式線程間通信的方式主要為共享內存、線程同步。
線程同步除了synchronized互斥同步外,也可以使用wait/notify實現等待、通知的機制。
(1)wait/notify屬于Object類的方法,但wait和notify方法調用,必須獲取對象的對象級別鎖,即synchronized同步方法或同步塊中使用。
(2)wait()方法:在其他線程調用此對象的 notify() 方法或 notifyAll() 方法前,或者其他某個線程中斷當前線程,導致當前線程一直阻塞等待。等同wait(0)方法。
wait(long timeout) 在其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量前,導致當前線程等待。 單位為毫秒。
void wait(long timeout, int nanos) 與 wait(long timeout) 不同的是增加了額外的納秒級別,更精細的等待時間控制。
(3)notfiy方法:喚醒在此對象監視器上等待的單個線程。選擇是任意性的,并在對實現做出決定時發生。線程通過調用其中一個 wait 方法,在對象的監視器上等待。
(4)notifyAll方法:喚醒在此對象監視器上等待的所有線程。
需要:wait被執行后,會自動釋放鎖,而notify被執行后,鎖沒有立刻釋放,由synchronized同步塊結束時釋放。
應用場景:簡單的生產、消費問題。
10、ThreadLocal與InheritableThreadLocal讓每個線程都有自己獨立的共享變量,有兩種方式:
一 該實例變量封存在線程類內部;如果該實例變量(非static)是引用類型,存在可能逸出的情況。
二 就是使用ThreadLocal在任意地方構建變量,即使是靜態的(static)。具有很好的隔離性。
(1)重寫initialValue()方法: 初始化ThreadLocal變量,解決get()返回null問題(
(2)InheritableThreadLocal 子線程可以讀取父線程的值,但反之不行
11、ReentrantLock的使用一個簡單的示例:
ReentrantLock 比 synchronized 功能更強大,主要體現:
(1)ReentrantLock 具有公平策略的選擇。
(2)ReentrantLock 可以在獲取鎖的時候,可有條件性地獲取,可以設置等待時間,很有效地避免死鎖。
如 tryLock() 和 tryLock(long timeout, TimeUnit unit)
(3)ReentrantLock 可以獲取鎖的各種信息,用于監控鎖的各種狀態。
(4)ReentrantLock 可以靈活實現多路通知,即Condition的運用。
————————————————————————————–
一、公平鎖與非公平鎖ReentrantLock 默認是非公平鎖,允許線程“搶占插隊”獲取鎖。公平鎖則是線程依照請求的順序獲取鎖,近似FIFO的策略方式。
二、鎖的使用:(1)lock() 阻塞式地獲取鎖,只有在獲取到鎖后才處理interrupt信息
(2)lockInterruptibly() 阻塞式地獲取鎖,立即處理interrupt信息,并拋出異常
(3)tryLock() 嘗試獲取鎖,不管成功失敗,都立即返回true、false,注意的是即使已將此鎖設置為使用公平排序策略,tryLock()仍然可以打開公平性去插隊搶占。如果希望遵守此鎖的公平設置,則使用 tryLock(0, TimeUnit.SECONDS),它幾乎是等效的(也檢測中斷)。
(4)tryLock(long timeout, TimeUnit unit)在timeout時間內阻塞式地獲取鎖,成功返回true,超時返回false,同時立即處理interrupt信息,并拋出異常。
如果想使用一個允許闖入公平鎖的定時 tryLock,那么可以將定時形式和不定時形式組合在一起:
if (lock.tryLock() || lock.tryLock(timeout, unit) ) { … }
三、條件Condition的使用條件Condition可以由鎖lock來創建,實現多路通知的機制。
具有await、signal、signalAll的方法,與wait/notify類似,需要在獲取鎖后方能調用。
12、ReentrantReadWriteLock的使用ReentrantReadWriteLock是對ReentrantLock 更進一步的擴展,實現了讀鎖readLock()(共享鎖)和寫鎖writeLock()(獨占鎖),實現讀寫分離。讀和讀之間不會互斥,讀和寫、寫和讀、寫和寫之間才會互斥,提升了讀寫的性能。
讀鎖示例:
private final java.util.concurrent.locks.ReadWriteLock lock = new ReentrantReadWriteLock();
public void method() {
try {
lock.readLock().lock();
//獲取到讀鎖readLock,同步塊
} finally {
lock.readLock().unlock();//釋放讀鎖readLock
}
}
寫鎖示例:
private final java.util.concurrent.locks.ReadWriteLock lock = new ReentrantReadWriteLock();
public void method() {
try {
lock.writeLock().lock();
//獲取到寫鎖writeLock,同步塊
} finally {
lock.writeLock().unlock();//釋放寫鎖writeLock
}
}
(1)同步容器
包括兩部分:
一個是早期JDK的Vector、Hashtable;
一個是它們的同系容器,JDK1.2加入的同步包裝類,使用Collections.synchronizedXxx工廠方法創建。
Map
同步容器都是線程安全的,一次只有一個線程訪問容器的狀態。
但在某些場景下可能需要加鎖來保護復合操作。
復合類操作如:新增、刪除、迭代、跳轉以及條件運算。
這些復合操作在多線程并發的修改容器時,可能會表現出意外的行為,
最經典的便是ConcurrentModificationException,
原因是當容器迭代的過程中,被并發的修改了內容,這是由于早期迭代器設計的時候并沒有考慮并發修改的問題。
其底層的機制無非就是用傳統的synchronized關鍵字對每個公用的方法都進行同步,使得每次只能有一個線程訪問容器的狀態。這很明顯不滿足我們今天互聯網時代高并發的需求,在保證線程安全的同時,也必須有足夠好的性能。
(2)并發容器
與Collections.synchronizedXxx()同步容器等相比,util.concurrent中引入的并發容器主要解決了兩個問題:
1)根據具體場景進行設計,盡量避免synchronized,提供并發性。
2)定義了一些并發安全的復合操作,并且保證并發環境下的迭代操作不會出錯。
util.concurrent中容器在迭代時,可以不封裝在synchronized中,可以保證不拋異常,但是未必每次看到的都是”最新的、當前的”數據。
1
Map
ConcurrentHashMap 替代同步的Map即(Collections.synchronized(new HashMap()))。眾所周知,HashMap是根據散列值分段存儲的,同步Map在同步的時候會鎖住整個Map,而ConcurrentHashMap在設計存儲的時候引入了段落Segment定義,同步的時候只需要鎖住根據散列值鎖住了散列值所在的段落即可,大幅度提升了性能。ConcurrentHashMap也增加了對常用復合操作的支持,比如”若沒有則添加”:putIfAbsent(),替換:replace()。這2個操作都是原子操作。注意的是ConcurrentHashMap 弱化了size()和isEmpty()方法,并發情況盡量少用,避免導致可能的加鎖(當然也可能不加鎖獲得值,如果map數量沒有變化的話)。
CopyOnWriteArrayList和CopyOnWriteArraySet分別代替List和Set,主要是在遍歷操作為主的情況下來代替同步的List和同步的Set,這也就是上面所述的思路:迭代過程要保證不出錯,除了加鎖,另外一種方法就是”克隆”容器對象。—缺點也明顯,占有內存,且數據最終一致,但數據實時不一定一致,一般用于讀多寫少的并發場景。
ConcurrentSkipListMap可以在高效并發中替代SoredMap(例如用Collections.synchronzedMap包裝的TreeMap)。
ConcurrentSkipListSet可以在高效并發中替代SoredSet(例如用Collections.synchronzedSet包裝的TreeMap)。
ConcurrentLinkedQuerue是一個先進先出的隊列。它是非阻塞隊列。注意盡量用isEmpty,而不是size();
14、CountDownLatch閉鎖的使用CountDownLatch是一個同步輔助類。
通常運用場景:
(1)作為啟動信號:將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,或入口。
通俗描述:田徑賽跑運動員等待(每位運動員為一個線程,都在await())的”發令槍”,當發令槍countDown(),喊0的時候,所有運動員跳過await()起跑線并發跑起來了。
(2)作為結束信號:在通過調用 countDown() 的線程打開入口前,所有調用 await 的線程都一直在入口處等待。用 N 初始化的 CountDownLatch 可以使一個線程在 N 個線程完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。
通俗描述:某裁判,在終點等待所有運動員都跑完,每個運動員跑完就計數一次(countDown())當為0時,就可以往下繼續統計第一人到最后一個撞線的時間。
更多的api:
boolean await(long timeout, TimeUnit unit) 使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷或超出了指定的等待時間。
15、CyclicBarrier關卡的使用CyclicBarrier是一個同步輔助類。
CyclicBarrier讓一個線程達到屏障時被阻塞,直到最后一個線程達到屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續執行
CyclicBarrier(int parties, Runnable barrierAction)構造函數,用于在所有線程都到達屏障后優先執行barrierAction的run()方法
使用場景:
可以用于多線程計算以后,最后使用合并計算結果的場景;
通俗描述:某裁判,在終點(await()阻塞處)等待所有運動員都跑完,所有人都跑完就可以做吃炸雞啤酒(barrierAction),但是只要一個人沒跑完就都不能吃炸雞啤酒,當然也沒規定他們同時跑(當然也可以,一起使用CountDownLatch)。
————————————————————————————–
CyclicBarrier與CountDownLatch的區別:
CountDownLatch強調的是一個線程等待多個線程完成某件事,只能用一次,無法重置;
CyclicBarrier強調的是多個線程互相等待完成,才去做某個事情,可以重置。
更多api:
int await(long timeout, TimeUnit unit) 在所有參與者都已經在此屏障上調用 await 方法之前將一直等待,或者超出了指定的等待時間。
16、Semaphore信號量的使用Semaphore信號量是一個計數信號量。
可以認為,Semaphore維護一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然后再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。
通俗描述:某個車庫只有N個車位,車主們要泊車,請向車庫保安處阻塞 acquire()等待獲取許可證,當獲得許可證,車主們才可以去泊車。當某個車主離開車位的時候,交還許可證release() ,從而其他阻塞等待的車主有機會獲得許可證。
另外:
Semaphore 默認是非公平策略,允許線程“搶占插隊”獲取許可證。公平策略則是線程依照請求的順序獲取許可證,近似FIFO的策略方式。
17、Executors框架(線程池)的使用(1)線程池是什么?
線程池是一種多線程的處理方式,利用已有線程對象繼續服務新的任務(按照一定的執行策略),而不是頻繁地創建銷毀線程對象,由此提供服務的吞吐能力,減少CPU的閑置時間。具體組成部分包括:
a、線程池管理器(ThreadPool)用于創建和管理線程池,包括創建線程池、銷毀線程池,添加新任務。
b、工作線程(Worker)線程池中的線程,閑置的時候處于等待狀態,可以循環回收利用。
c、任務接口(Task)每個任務必須實現的接口類,為工作線程提供調用,主要規定了任務的入口、任務完成的收尾工作、任務的狀態。
d、等待隊列(Queue)存放等待處理的任務,提供緩沖機制。
(2)Executors框架常見的執行策略
Executors框架提供了一些便利的執行策略。
java.util.concurrent.ExecutorService service = java.util.concurrent.Executors.newFixedThreadPool(100);
newSingleThreadExecutor:創建一個單線程的線程池。
這個線程池只有一個線程在工作,也就是相當于單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
newFixedThreadPool:創建固定大小的線程池。
每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程。
newCachedThreadPool:創建一個可緩存的線程池。
如果線程池的大小超過了處理任務所需要的線程,那么就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(或者說JVM)能夠創建的最大線程大小。
newScheduledThreadPool:創建一個大小無限的線程池。
此線程池支持定時以及周期性執行任務的需求。
newSingleThreadScheduledExecutor:創建一個單線程的線程池。
此線程池支持定時以及周期性執行任務的需求。
(3)ExecutorService線程池管理
ExecutorService的生命周期有3個狀態:運行、關閉(shutting down)、停止。
提交任務submit(xxx)擴展了基本方法 Executor.execute(java.lang.Runnable)。
Future> submit(Runnable task) 提交一個 Runnable 任務用于執行,并返回一個表示該任務的 Future。
shutdown() 啟動一次順序關閉,執行以前提交的任務,但不接受新任務。
List
(3)ThreadPoolExecutor機制
ThreadPoolExecutor為Executors的線程池內部實現類。
構造函數詳解:
ThreadPoolExecutor線程池管理機制:
1.當線程池小于corePoolSize時,新提交任務將創建一個新線程執行任務,即使此時線程池中存在空閑線程。
2.當線程池達到corePoolSize時,新提交任務將被放入workQueue中,等待線程池中任務調度執行
3.當workQueue已滿,且maximumPoolSize>corePoolSize時,新提交任務會創建新線程執行任務
4.當提交任務數超過maximumPoolSize時,新提交任務由RejectedExecutionHandler處理
5.當線程池中超過corePoolSize線程,空閑時間達到keepAliveTime時,關閉空閑線程
6.當設置allowCoreThreadTimeOut(true)時,線程池中corePoolSize線程空閑時間達到keepAliveTime也將關閉
18、可攜帶結果的任務Callable 和 Future / FutureTask(1)為解決Runnable接口不能返回一個值或受檢查的異常,可以采用Callable接口實現一個任務。
(2)Future表示異步計算的結果,可以對于具體的Runnable或者Callable任務進行查詢是否完成,查詢是否取消,獲取執行結果,取消任務等操作。
V get() throws InterruptedException, ExecutionException 如有必要,等待計算完成,然后獲取其結果。
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException 如有必要,最多等待為使計算完成所給定的時間之后,獲取其結果(如果結果可用)。
(3)FutureTask
FutureTask則是一個RunnableFuture
注意的是提交到CompletionService中的Future是按照完成的順序排列的,而不是按照添加的順序排列的。
19、Atomic系列-原子變量類其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具有排他性,即當某個線程進入方法,執行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執行完成,才由JVM從等待隊列中選擇一個另一個線程進入,這只是一種邏輯上的理解。實際上是借助硬件的相關指令來實現的,不會阻塞線程(或者說只是在硬件級別上阻塞了)。其中的類可以分成4組
基本類:AtomicInteger、AtomicLong、AtomicBoolean;
引用類型:AtomicReference、AtomicStampedRerence、AtomicMarkableReference;–AtomicStampedReference 或者 AtomicMarkableReference 解決線程并發中,導致的ABA問題
數組類型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray —數組長度固定不可變,但保證數組上每個元素的操作絕對安全的
屬性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
Updater使用限制:
限制1:操作的目標不能是static類型,前面說到unsafe的已經可以猜測到它提取的是非static類型的屬性偏移量,如果是static類型在獲取時如果沒有使用對應的方法是會報錯的,而這個Updater并沒有使用對應的方法。
限制2:操作的目標不能是final類型的,因為final根本沒法修改。
限制3:必須是volatile類型的數據,也就是數據本身是讀一致的。
限制4:屬性必須對當前的Updater所在的區域是可見的,也就是private如果不是當前類肯定是不可見的,protected如果不存在父子關系也是不可見的,default如果不是在同一個package下也是不可見的。
簡單示例:
static class A {
volatile int intValue = 100;
}
private AtomicIntegerFieldUpdater atomicIntegerFieldUpdater
= AtomicIntegerFieldUpdater.newUpdater(A.class, "intValue");
多線程并發編程主要培養編程者深入了解最底層的運作原理,加強編程者邏輯思維,這樣才能寫出高效、安全、可靠的多線程并發程序。
說到這里,順便給大家推薦一個架構方面的交流學習群:650385180,里面會分享一些資深架構師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發、高性能、分布式、微服務架構的原理,JVM性能優化這些成為架構師必備的知識體系。還能領取免費的學習資源,文章開頭的知識體系圖也是在群里面獲取的,相信對于已經工作和遇到技術瓶頸的碼友,在這個群里會有你需要的內容。
什么叫線程安全?線程安全就是每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的。
線程安全就是說多線程訪問同一代碼,不會產生不確定的結果。
線程安全問題多是由全局變量和靜態變量引起的,當多個線程對共享數據只執行讀操作,不執行寫操作時,一般是線程安全的;當多個線程都執行寫操作時,需要考慮線程同步來解決線程安全問題。
什么叫線程同步?多個線程操作一個資源的情況下,導致資源數據前后不一致。這樣就需要協調線程的調度,即線程同步。 解決多個線程使用共通資源的方法是:線程操作資源時獨占資源,其他線程不能訪問資源。使用鎖可以保證在某一代碼段上只有一條線程訪問共用資源。
有兩種方式實現線程同步:
1、synchronized
2、同步鎖(Lock)
什么叫線程通信?有時候線程之間需要協作和通信。
有兩種方式實現線程通信:
1、synchronized 實現內存可見性,滿足線程共享變量
2、wait/notifynotifyAll(synchronized同步方法或同步塊中使用) 實現內存可見性,及生產消費模式的相互喚醒機制
3、同步鎖(Lock)的Condition(awaitsignalsignalAll)
4、管道,實現數據的共享,滿足讀寫模式
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76473.html
摘要:相比與其他操作系統包括其他類系統有很多的優點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少。因為多線程競爭鎖時會引起上下文切換。減少線程的使用。很多編程語言中都有協程。所以如何避免死鎖的產生,在我們使用并發編程時至關重要。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)syn...
摘要:多線程不僅是后端開發面試中非常熱門的一個問題,也是各種高級工具框架與分布式的核心基石。有興趣的讀者可以參考本系列的第一篇文章來了解一下并發相關的基本概念當我們在說并發多線程,說的是什么。 多線程不僅是Java后端開發面試中非常熱門的一個問題,也是各種高級工具、框架與分布式的核心基石。但是這個領域相關的知識點涉及到了線程調度、線程同步,甚至在一些關鍵點上還涉及到了硬件原語、操作系統等更底...
摘要:大多數待遇豐厚的開發職位都要求開發者精通多線程技術并且有豐富的程序開發調試優化經驗,所以線程相關的問題在面試中經常會被提到。掌握了這些技巧,你就可以輕松應對多線程和并發面試了。進入等待通行準許時,所提供的對象。 最近看到網上流傳著,各種面試經驗及面試題,往往都是一大堆技術題目貼上去,而沒有答案。 不管你是新程序員還是老手,你一定在面試中遇到過有關線程的問題。Java語言一個重要的特點就...
摘要:三關鍵字能保證原子性嗎并發編程藝術這本書上說保證但是在自增操作非原子操作上不保證,多線程編程核心藝術這本書說不保證。多線程訪問關鍵字不會發生阻塞,而關鍵字可能會發生阻塞關鍵字能保證數據的可見性,但不能保證數據的原子性。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchroniz...
閱讀 3324·2023-04-25 16:25
閱讀 3837·2021-11-15 18:01
閱讀 1609·2021-09-10 11:21
閱讀 3016·2021-08-02 16:53
閱讀 3087·2019-08-30 15:55
閱讀 2494·2019-08-29 16:24
閱讀 2104·2019-08-29 13:14
閱讀 1037·2019-08-29 13:00