關注我,能夠獲取最新知識、經典面試題以及技術分享java
多線程和併發是求職大小廠面試中必問的知識點,其涉及到點不少,難度很大。有些人面對這些問題有點迷茫,爲了解決這狀況,總結了一下java多線程併發的基礎知識點。並且要想深刻研究java多線程併發也必須先掌握基礎知識,可爲後續各個模塊深刻研究作好作好準備。如今廢話很少說,各位看官請查看基礎知識點,後續還有源碼解析(synchronize
底層原理,線程池原理,Lock
,AQS
,同步、併發容器等源碼解析)。面試
程序: 是計算機指令的集合,它以文件的形式存儲在磁盤上,即程序是靜態的代碼數據庫
進程:設計模式
線程:數組
三者之間的關係:緩存
內存機制可查看文章《推薦收藏系列:一文理解JVM虛擬機(內存、垃圾回收、性能優化)解決面試中遇到問題》安全
組成部分:虛擬CPU、執行的代碼以及處理的數據。 性能優化
進程: 指系統中正在運行中的應用程序,它擁有本身獨立的內存空間;多線程
線程: 是指進程中一個執行流程,一個進程中容許同時啓動多個線程,他們分別執行不一樣的任務,多個線程共享內存,從而極大地提升了程序的運行效率;併發
主要區別:
使用多線程好處:
Java程序啓動時,一個線程馬上運行,它執行main方法,這個線程稱爲程序的主線程,任何Java程序都至少有一個線程,即主線程。
主線程的特殊之處在於:
單核計算機只有一個CPU,各個線程輪流得到CPU的使用權,才能執行任務:
Thread
類有以下3個靜態常量來表示優先級:
線程狀態(State
枚舉值表明線程狀態):
Thread thread = new Thread()
。start
方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取 cpu 的使用權。run()
方法),此時除非此線程自動放棄 CPU 資源或者有優先級更高的線程進入,線程將一直運行到結束sleep
,suspend
,wait
等方法均可以致使線程阻塞WAITING
,它能夠在指定的時間後自行返回。run
方法執行結束或者調用stop
方法後,該線程就會死亡。對於已經死亡的線程,沒法再使用start
方法令其進入就緒。 線程在Running的過程當中可能會遇到阻塞(Blocked)狀況:
join()
和sleep()
方法,sleep()
時間結束或被打斷,join()
中斷,IO完成都會回到Runnable
狀態,等待JVM的調度。wait()
,使該線程處於等待池(wait blocked pool),直到notify()
/notifyAll()
,線程被喚醒被放到鎖定池(lock blocked pool ),釋放同步鎖使線程回到可運行狀態(Runnable) 線程建立方式:
run()
,無返回值run()
1.實現Runnable接口,重載run()
,無返回值,Runnable接口的存在主要是爲了解決Java中不容許多繼承的問題。
public class ThreadRunnable implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadMain {
public static void main(String[] args) throws Exception {
ThreadRunnable threadRunnable1 = new ThreadRunnable();
ThreadRunnable threadRunnable2 = new ThreadRunnable();
ThreadRunnable threadRunnable3 = new ThreadRunnable();
Thread thread1 = new Thread(threadRunnable1);
Thread thread2 = new Thread(threadRunnable2);
Thread thread3 = new Thread(threadRunnable3);
thread1.start();
thread2.start();
thread3.start();
}
}
複製代碼
2.繼承Thread類,重寫run()
,經過調用Thread的start()
會調用建立線程的run()
,不一樣線程的run方法裏面的代碼交替執行。但因爲Java不支持多繼承.所以繼承Thread類就表明這個子類不能繼承其餘類.
public class ThreadCustom extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread() + ":" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args)
{
ThreadCustom thread = new ThreadCustom();
thread.start();
}
}
複製代碼
3.實現Callable接口,經過FutureTask/Future來建立有返回值的Thread線程,經過Executor執行,該方式有返回值,能夠得到異步。
public class ThreadCallableCustom {
public static void main(String[] args) throws Exception {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
public Integer call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return 1;
}
});
Executor executor = Executors.newFixedThreadPool(1);
((ExecutorService) executor).submit(futureTask);
//得到線程執行狀態
System.out.println(Thread.currentThread().getName() + ":" + futureTask.get());
}
}
複製代碼
4.使用Executors建立ExecutorService,入參Callable或Future,適用於線程池和併發
public class ThreadExecutors {
private final String threadName;
public ThreadExecutors(String threadName) {
this.threadName = threadName;
}
private ThreadFactory createThread() {
ThreadFactory tf = new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread thread = new Thread();
thread.setName(threadName);
thread.setDaemon(true);
try {
sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
return thread;
}
};
return tf;
}
public Object runCallable(Callable callable) {
return Executors.newSingleThreadExecutor(createThread()).submit(callable);
}
public Object runFunture(Runnable runnable) {
return Executors.newSingleThreadExecutor(createThread()).submit(runnable);
}
}
public class ThreadTest {
public static void main(String[] args) throws Exception {
ThreadExecutors threadExecutors = new ThreadExecutors("callableThread");
threadExecutors.runCallable(new Callable() {
public String call() throws Exception {
return "success";
}
});
threadExecutors.runFunture(new Runnable() {
public void run() {
System.out.println("execute runnable thread.");
}
});
}
}
複製代碼
1)兩個接口須要實現的方法名不同,Runnable須要實現的方法爲run()
,Callable須要實現的方法爲call()
。
2)實現的方法返回值不同,Runnable任務執行後無返回值,Callable任務執行後能夠獲得異步計算的結果。
3)拋出異常不同,Runnable不能夠拋出異常,Callable能夠拋出異常。
線程安全定義
當多個線程訪問某個一類(對象或方法)時,這個類始終都能表現出正確的行爲,那麼這個類(對象或方法)就是線程安全的(即在多線程環境中被調用時,可以正確地處理多個線程之間的共享變量,使程序功能正確完成)。
線程安全示例
餓漢式單例模式-線程安全
public class EagerSingleton(){
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton(){};
public static EagerSingleton getInstance(){
return instance;
}
}
複製代碼
如何解決線程安全問題?
能夠經過加鎖的方式:
lock()
以及釋放同步鎖unlock()
死鎖,是指兩個或兩個以上的進程(或線程)在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。
活鎖,任務或者執行者沒有被阻塞,因爲某些條件沒有知足,致使一直重複嘗試,失敗,嘗試,失敗。
產生死鎖的必要條件:
死鎖的解決方法:
1)悲觀鎖
悲觀鎖,老是假設最壞的狀況,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。
2)樂觀鎖
樂觀鎖,顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣能夠提升吞吐量。
多個線程間鎖的併發控制,對象鎖多個線程、每一個線程持有該方法所屬對象的鎖以及類鎖。synchronized, wait, notify 是任何對象都具備的同步工具
對象鎖的同步和異步
同步的目的就是爲線程安全,其實對於線程安全來講,須要知足兩個特性:原子性(同步)、可見性。
Volatile做用,實現變量在多個線程間可見,保證內存可見性和禁止指令重排
多線程的內存模型:main memory(主存)、working
memory(線程棧),在處理數據時,線程會把值從主存load到本地棧,完成操做後再save回去(volatile關鍵詞的做用:每次針對該變量的操做都激發一次load and save)。
線程局部變量,以空間換時間的手段,爲每一個線程提供變量的獨立副本,以無鎖的狀況下保障線程安全。主要解決的就是讓每一個線程執行完成以後再結束,這個時候就要用到join()方法。
適用場景:
1). 線程同步,是指線程之間所具備的一種制約關係,一個線程的執行依賴另外一個線程的消息,當它沒有獲得另外一個線程的消息時應等待,直到消息到達時才被喚醒。
線程間的同步方法,大致可分爲兩類:用戶模式和內核模式。顧名思義:
內核模式,就是指利用系統內核對象的單一性來進行同步,使用時須要切換內核態與用戶態。內核模式下的方法有:
用戶模式,就是不須要切換到內核態,只在用戶態完成操做。用戶模式下的方法有:
2). 線程互斥,是指對於共享的進程系統資源,在各單個線程訪問時的排它性。
當有若干個線程都要使用某一共享資源時,任什麼時候刻最多隻容許一個線程去使用,其它要使用該資源的線程必須等待,直到佔用資源者釋放該資源。 線程互斥能夠當作是一種特殊的線程同步。
線程是操做系統中獨立的個體,但這些個體之間若是不通過特殊的協做就不能成爲一個總體,線程間的通訊就成爲總體的必用方式之一。
線程間通訊的幾種方式?
線程之間的通訊方式:
共享內存:在共享內存的併發模型裏,線程之間共享程序的公共狀態,線程之間經過寫-讀內存中的公共狀態來隱式進行通訊。典型的共享內存通訊方式,就是經過共享對象進行通訊。
消息傳遞:在消息傳遞的併發模型裏,線程之間沒有公共狀態,線程之間必須經過明確的發送消息來顯式進行通訊。在 Java 中典型的消息傳遞方式,就是wait()
和 notify()
,或者 BlockingQueue 。
java.util.concurrent.locks.Lock 接口,比 synchronized 提供更具拓展行的鎖操做。它容許更靈活的結構,能夠具備徹底不一樣的性質,而且能夠支持多個相關類的條件對象。它的優點有:
AQS ,AbstractQueuedSynchronizer ,即隊列同步器。它是構建鎖或者其餘同步組件的基礎框架(如 ReentrantLock、ReentrantReadWriteLock、Semaphore 等),J.U.C 併發包的做者(Doug Lea)指望它可以成爲實現大部分同步需求的基礎。它是 J.U.C 併發包中的核心基礎組件。
優點:
AQS 解決了在實現同步器時涉及當的大量細節問題,例如獲取同步狀態、FIFO 同步隊列。基於 AQS 來構建同步器能夠帶來不少好處。它不只可以極大地減小實現工做,並且也沒必要處理在多個位置上發生的競爭問題。
在基於 AQS 構建的同步器中,只能在一個時刻發生阻塞,從而下降上下文切換的開銷,提升了吞吐量。同時在設計 AQS 時充分考慮了可伸縮性,所以 J.U.C 中,全部基於 AQS 構建的同步器都可以得到這個優點。
何爲同步容器?能夠簡單地理解爲經過synchronized來實現同步的容器,若是有多個線程調用同步容器的方法,它們將會串行執行。
特色:
常見同步類容器:
jdk5.0之後提供了多種併發類容器來替代同步類容器從而改善性能。
同步類容器侷限性:
經常使用的併發類容器:
ConcurrentHashMap原理
Copy-On-Write容器
Copy-On-Write簡稱COW,是一種用於程序設計中的優化策略。
JDK裏的COW容器有兩種:
併發Queue:
ConcurrentLinkedQueue
經常使用方法:
BlockingQueue接口實現:
CountDownLatch: 用於監聽某些初始化操做,等初始化執行完畢後,通知主線程繼續工做。
CycilcBarrier: 全部線程都準備好後,才一塊兒出發,只要有一我的沒有準備好,你們都等待。
Concurrent.util經常使用類 定義:實現異步回調,jdk針對該場景提供了一個實現的封裝,簡化了調用 適合場景:處理耗時的業務邏輯時,可有效的減小系統的響應時間,提升系統的吞吐量。
Concurrent.util經常使用類 Semaphore:信號量,適合高併發訪問, 用於進行訪問流量的控制
ReentrantLock(重入鎖) 重入鎖,在須要進行同步的代碼部分加上鎖定,但不要忘記最後必定要釋放鎖定,否則會形成鎖永遠沒法釋放,其餘線程永遠也進不來的結果。
鎖與等待/通知
多Condition
ReentrantReadWriteLock(讀寫鎖)
使用 Executor 框架的緣由:
線程池的建立方式:
普通任務線程池
定時任務線程池
線程池的關閉方式
ThreadPoolExecutor 提供了兩個方法,用於線程池的關閉,分別是:
因爲java多線程併發涉及到的知識點太多了,這邊不可能一一列全,不過我會在後續的更新中一一去補充完善,並且會涉及原理以及源碼層次的解析。謝謝觀看,有錯誤歡迎指出更改!!
關注公衆號【Ccww筆記】,一塊兒學習。天天會分享乾貨,最新知識、經典面試題以及技術分享!!
最後麻煩各位看官點個贊,謝謝支持!!!