最近也是在後臺收到很多小伙伴私信問我線程和線程池這一塊的問題,說自己在面試的時候老是被問到這一塊的問題,被問的很頭疼。前幾天看到後幫幾個小伙伴解決了問題,但是問的人有點多我一個個回答也回答不過來,乾脆花了一個上午時間寫了這篇文章分享給大家。話不多說,滿滿的乾貨都在下麵了! ...
前言:
最近也是在後臺收到很多小伙伴私信問我線程和線程池這一塊的問題,說自己在面試的時候老是被問到這一塊的問題,被問的很頭疼。前幾天看到後幫幾個小伙伴解決了問題,但是問的人有點多我一個個回答也回答不過來,乾脆花了一個上午時間寫了這篇文章分享給大家。話不多說,滿滿的乾貨都在下麵了!
併發與並行
併發:指兩個或多個事件在同一個時間段內發生。 在操作系統中,安裝了多個程式,併發指的是在一段時間內巨集觀上有多個程式同時運行,這在單 CPU 系統中,每 一時刻只能有一道程式執行,即微觀上這些程式是分時的交替運行,只不過是給人的感覺是同時運行,那是因為分 時交替運行的時間是非常短的。
並行:指兩個或多個事件在同一時刻發生(同時發生)。 在多個 CPU 系統中,這些可以併發執行的程式便可以分配到多個處理器上(CPU),實現多任務並行執行,即利用每個處理器來處理一個可以併發執行的程式,這樣多個程式便可以同時執行。目前電腦市場上說的多核 CPU,便是多核處理器,核越多,能夠並行處理的程式數量越多,這能大大的提高電腦運行的效率。
註意:單核處理器的電腦肯定是不能並行的處理多個任務的,只能是多個任務在單個CPU上併發運行。同理,線程也是一樣的,從巨集觀角度上理解線程是並行運行的,但是從微觀角度上分析卻是串列運行的,即一個線程一個線程的去運行,當系統只有一個CPU時,線程會以某種順序執行多個線程,我們把這種情況稱之為線程調度。
線程與進程
進程:是指一個記憶體中運行的應用程式,每個進程都有一個獨立的記憶體空間,一個應用程式可以同時運行多 個進程;進程也是程式的一次執行過程,是系統運行程式的基本單位;系統運行一個程式即是一個進程從創 建、運行到消亡的過程。 線程:線程是進程中的一個執行單元,負責當前進程中程式的執行,一個進程中至少有一個線程。一個進程 中是可以有多個線程的,這個應用程式也可以稱之為多線程程式
創建線程類
Java使用 java.lang.Thread 類代表線程,所有的線程對象都必須是Thread類或其子類的實例。每個線程的作用是完成一定的任務,實際上就是執行一段程式流即一段順序執行的代碼。Java使用線程執行體來代表這段程式流。 Java中通過繼承Thread類來創建並啟動多線程的步驟如下:
定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,因此把 run()方法稱為線程執行體。 創建Thread子類的實例,即創建了線程對象。 調用線程對象的start()方法來啟動該線程。 首先自定義一個線程類
public class ThreadClass extends Thread { //重寫run方法 @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"正在執行"+i); try { //休眠500毫秒 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
主線程:
public class DemoTest { public static void main(String[] args) { //創建一個線程對象 ThreadClass mythread = new ThreadClass(); //開啟線程 mythread.start(); for (int i = 0; i < 10; i++) { System.out.println("主線程正在執行" + i); try { //休眠500毫秒 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
其實實際上我們一般不會去繼承線程類,由於java的單繼承特性,當我們繼承了線程類就無法繼承別的父類了,一般我們是通過重寫介面來開啟線程的。
重寫Runnable介面
步驟如下:
定義Runnable介面的實現類,並重寫該介面的run()方法,該run()方法的方法體同樣是該線程的線程執行體。 創建Runnable實現類的實例,並以此實例作為Thread的target來創建Thread對象,該Thread對象才是真正 的線程對象。 調用線程對象的start()方法來啟動線程。 首先重寫介面
public class Runnableimp implements Runnable { //重寫run方法 @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"正在執行"+i); try { //休眠500毫秒 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
主線程:
public class DemoTest { public static void main(String[] args) { //創建一個線程對象,傳入重寫了run方法的介面對象 Thread mythread = new Thread(new Runnableimp()); //開啟線程 mythread.start(); for (int i = 0; i < 10; i++) { System.out.println("主線程正在執行" + i); try { //休眠500毫秒 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
執行的結果和剛纔相同。
匿名內部類方式實現線程的創建
public class DemoTest { public static void main(String[] args) { //創建一個線程對象,使用匿名內部類重寫run方法 Thread mythread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"正在執行"+i); try { //休眠500毫秒 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); //開啟線程 mythread.start(); } }
使用lambda表達式
public class DemoTest { public static void main(String[] args) { //創建一個線程對象,使用lambda表達式重寫run方法 Thread mythread = new Thread(()->{ for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"正在執行"+i); try { //休眠500毫秒 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }); //開啟線程 mythread.start(); } }
線程安全
線程安全問題都是由全局變數及靜態變數引起的。若每個線程中對全局變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全局變數是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步, 否則的話就可能影響線程安全。
線程同步
當我們使用多個線程訪問同一資源的時候,且多個線程中對資源有寫的操作,就容易出現線程安全問題。 要解決上述多線程併發訪問一個資源的安全性問題,Java中提供了同步機制 (synchronized)來解決。
同步代碼塊
同步代碼塊: synchronized 關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。 格式:
synchronized(同步鎖){ 需要同步操作的代碼 }
示例
private int num = 100; private Object lock = new Object(); synchronized (lock) { num--; }
同步方法
同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外 等著。
public synchronized void method() { 可能會產生線程安全問題的代碼 }
Lock鎖
java.util.concurrent.locks.Lock 機制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作, 同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向對象。 Lock鎖也稱同步鎖,加鎖與釋放鎖如下:
public void lock() :加同步鎖。 public void unlock() :釋放同步鎖。
Lock lock = new ReentrantLock(); //加鎖 lock.lock(); 可能會產生線程安全問題的代碼 //釋放鎖 lock.unlock();
我們使用線程的時候就去創建一個線程,這樣實現起來非常簡便,但是就會有一個問題: 如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷毀線程需要時間。 那麼有沒有一種辦法使得線程可以復用,就是執行完一個任務,並不被銷毀,而是可以繼續執行其他的任務? 在Java中可以通過線程池來達到這樣的效果。今天我們就來詳細講解一下Java的線程池。
線程池
線程池:其實就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作, 無需反覆創建線程而消耗過多資源。
Java裡面線程池的頂級介面是
java.util.concurrent.Executor ,但是嚴格意義上講 Executor 並不是一個線程 池,而只是一個執行線程的工具。真正的線程池介面是
java.util.concurrent.ExecutorService 。 要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優 的,因此在
java.util.concurrent.Executors線程工廠類裡面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用Executors工程類來創建線程池對象。 newFixedThreadPool方法
public static ExecutorService newFixedThreadPool(int nThreads)
創建一個可重用固定線程數的線程池,以共用的無界隊列方式來運行這些線程。在任意點,在大多數 nThreads 線程會處於處理任務的活動狀態。如果在所有線程處於活動狀態時提交附加任務,則在有可用線程之前,附加任務將在隊列中等待。如果在關閉前的執行期間由於失敗而導致任何線程終止,那麼一個新線程將代替它執行後續的任務(如果需要)。在某個線程被顯式地關閉之前,池中的線程將一直存在。
參數:
nThreads - 池中的線程數
返回:
新創建的線程池
拋出:
IllegalArgumentException - 如果 nThreads <= 0 獲取到了一個線程池ExecutorService 對象,那麼怎麼使用呢,在這裡定義了一個使用線程池對象的方法如下: public Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行。
下麵的代碼通過四種方式向線程池中提交任務執行
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo01 { public static void main(String[] args) throws InterruptedException { //創建線程池,線程數量為2 ExecutorService es = Executors.newFixedThreadPool(2); //將任務扔到線程池的四種方式 //使用匿名內部類, es.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"正在執行"+i); try { //休眠500毫秒 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); //使用lambda表達式 es.submit(()->{ for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"正在執行"+i); try { //休眠500毫秒 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }); //使用重寫的介面 es.submit(new Runnableimp()); //使用重寫的線程類 es.submit(new ThreadClass()); //啟動一次順序關閉,執行以前提交的任務,但不接受新任務 es.shutdown(); //主線程等待所有線程將任務執行完畢 while (!es.isTerminated()); System.out.println("線程執行完畢!"); } }
除此之外java還提供了:
newScheduledThreadPool:創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行。 newSingleThreadExecutor:創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。
newSingleThreadScheduledExecutor: 創建一個單線程執行程式,它可安排在給定延遲後運行命令或者定期 地執行。
小結:
最後小編整理了一份Java相關的資料,需要的小伙伴可以加我微信即可免費領取!
放一些大概截圖,感興趣的小伙伴可以收著。