1. 程式人生 > >面經總結:多線程

面經總結:多線程

計算 選擇 new 好的 共享資源 keepal join 關於 刷新

  • 多線程的實現?

三種方法:1.繼承Thread類;2.實現Runnable接口;3.使用Executor創建線程池;

  • 多線程的的同步/線程安全的方式?

(1)同步方法:synchronized修飾的方法;

(2)同步代碼塊:同步是一種高開銷的操作,因此應該盡量減少同步的內容。通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。

  同步方法和同步代碼塊的區別是什麽?

  答:同步方法默認用this或者當前類class對象作為鎖; 同步代碼塊可以選擇以什麽來加鎖,比同步方法要更細顆粒度,我們可以選擇只同步會發生同步問題的部分代碼而不是整個方法。

(3)使用volatile實現同步:每次線程要訪問volatile修飾的變量時都是從內存中讀取,而不是從緩存當中讀取,因此每個線程訪問到的變量值都是一樣的。這樣就保證了同步。

(4)使用重入鎖實現線程同步:ReentrantLock是concurrent包的類;常用方法有lock()和unlock();可以創建公平鎖;支持非阻塞的tryLock(可超時);需要手動釋放鎖。

(5)使用ThreadLocal實現線程同步:每個線程都創建一個變量副本,修改副本不會影響其他線程的副本。ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個線程對相同資源的並發訪問,是為了多個線程之間進行通信的有效方式;而ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量)。

  • 多線程的優化?

影響多線程性能的問題:死鎖、過多串行化、過多鎖競爭等;

預防和處理死鎖的方法:

  1)盡量不要在釋放鎖之前競爭其他鎖;一般可以通過細化同步方法來實現;

  2)順序索取鎖資源;

  3)嘗試定時鎖tryLock();

降低鎖競爭方法:

  1)縮小鎖的範圍,減小鎖的粒度;

  2)使用讀寫分離鎖ReadWriteLock來替換獨占鎖:來實現讀-讀並發,讀-寫串行,寫-寫串行的特性。這種方式更進一步提高了可並發性,因為有些場景大部分是讀操作,因此沒必要串行工作。

  • 線程池有哪幾種?有哪些參數?

(1.創建;2.四類線程池;3.參數;)

創建:線程池的頂級接口是Executor,是執行線程的工具;真正的線程接口是ExecutorService;

ThreadPoolExecutor是ExecutorService的默認實現;

示例:

ExecutorService pool=Executors.newFixedThreadPool(2);

四類線程池:

1. newSingleThreadExecutor

創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那麽會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

2.newFixedThreadPool

創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麽線程池會補充一個新線程。

3. newCachedThreadPool

創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,

那麽就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。

4.newScheduledThreadPool

創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。

線程池ThreadPoolExecutor的參數:

corePoolSize - 池中所保存的線程數,包括空閑線程。

maximumPoolSize-池中允許的最大線程數。

keepAliveTime - 當線程數大於核心時,此為終止前多余的空閑線程等待新任務的最長時間。

unit - keepAliveTime 參數的時間單位。

workQueue - 執行前用於保持任務的隊列。此隊列僅保持由 execute方法提交的 Runnable任務。

threadFactory - 執行程序創建新線程時使用的工廠。

handler - 由於超出線程範圍和隊列容量而使執行被阻塞時所使用的處理程序。

關於corePoolSize和maximumPoolSize:

如果線程數<corePoolSize,有任務時,無論是否有空閑線程,都會創建新線程;

如果corePoolSize<線程數<maximumPoolSize,大於的部分放到任務隊列,直到隊列滿了, 才會創建小於等於maximumPoolSize的線程。

  • 線程的狀態有哪些?

根據java.lang.Thread.State類,線程包括六個狀態:

(NEW、RUNNABLE、BLOCKED、WAITTING、TIME_WAITTING、TERMINATED)

NEW:線程實例化還未執行start();

RUNNABLE:線程已經在JVM執行。(但還在等待cpu資源?存疑。似乎Running狀態是包含在Runnable;Runnable就是線程執行的狀態)

BLOCKED:線程阻塞;等待鎖,

WAITTING:線程等待;調用Object.wait() 沒有timeout 或者 Thread.join() 沒有timeout 時進入該狀態;

TIMED_WAITTING:線程等待;調用Thread.sleep、Object.wait(timeout) 或者 Thread.join(timeout)是進入該狀態;

TERMINATED:線程終止;

(相比較原版本,DEAD更改為TERMINATED,沒有Running狀態,有WAITTING和TIMED_WAITTING狀態;)

狀態變化:

(和原版本的區別,沒有Running狀態;等待不屬於阻塞、sleep/join不屬於阻塞!)

 Runnable:t.start();從new變為Runnable;

 Waitting:Runnable執行wait()/join();無限等待喚醒;

 Timed_Waitting:Runnable執行sleep()/wait(timeout)/join(timeout); wait釋放鎖,sleep()不釋放鎖;等待喚醒,但設置了時限;

 Blocked:線程在等待鎖;

  • wait和sleep區別?

(1.屬性;2.鎖;3.範圍;)   1)屬性:wait()是Object的方法,而sleep()是線程類Thread的方法;   2)鎖:wait會放棄對象鎖,進入等待隊列,只有調用了notify(),才會結束wait狀態,參與競爭同步資源鎖;而sleep()只讓出了cpu,而不會釋放同步資源鎖,sleep指定時間後CPU再回到該線程繼續往下執行;   3)範圍:sleep可以在任何地方使用,wait只能在同步方法或同步塊中使用;
  • volatile的作用?

在當前的Java內存模型下,線程可以把變量保存在本地內存(比如機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續使用它在寄存器中的變量值的拷貝,造成數據的不一致。

  要解決這個問題,只需要像在本程序中的這樣,把該變量聲明為volatile(不穩定的)即可,這就指示JVM,這個變量是不穩定的,每次使用它都到主存中進行讀取。一般說來,多任務環境下各任務間共享的標誌都應該加volatile修飾。

  Volatile修飾的成員變量在每次被線程訪問時,都強迫從共享內存中重讀該成員變量的值。而且,當成員變量發生變化時,強迫線程將變化值回寫到共享內存。這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。

  使用volatile關鍵字修飾變量,線程要訪問變量時都是從內存中讀取,而不是從緩存當中讀取,因此每個線程訪問到的變量值都是一樣的。

  • synchronized和lock的區別?

(1.屬性;2.trylock;3.釋放;4.線程交互)   1)lock是接口,syn是Java中的關鍵字;   2)lock的tryLock方法可以選擇性獲取鎖,能夠規避死鎖;而syn會一直獲取下去;   3)syn發生異常或者同步塊結束的時候,會自動釋放鎖;而Lock必須手動釋放unlock;   4)syn線程交互,用到的是同步對象的wait/notify/notifyAll方法;lock得到一個Condition對象,調用Condition對象的await/signal/signalAll;

tryLock(),避免死鎖;synchronized是不公平鎖,而Lock可以指定鎖公平還是不公平;

syn實現wait/notify機制通知的線程是隨機的,Lock可以有選擇性的通知。

  • synchronized和volatile的內存可見性和原子性?

可見性與原子性 可見性:一個線程對共享變量的修改,更夠及時的被其他線程看到
原子性:即不可再分了,不能分為多步操作。比如賦值或者return。比如"a = 1;"和 "return a;"這樣的操作都具有原子性。類似"a += b"這樣的操作不具有原子性,在某些JVM中"a += b"可能要經過這樣三個步驟:
① 取出a和b
② 計算a+b
③ 將計算結果寫入內存 (1)Synchronized:保證可見性和原子性
Synchronized能夠實現原子性和可見性;在Java內存模型中,synchronized規定,線程在加鎖時,先清空工作內存→在主內存中拷貝最新變量的副本到工作內存→執行完代碼→將更改後的共享變量的值刷新到主內存中→釋放互斥鎖。 (2)Volatile:保證可見性,但不保證操作的原子性
Volatile實現內存可見性是通過store和load指令完成的;也就是對volatile變量執行寫操作時,會在寫操作後加入一條store指令,即強迫線程將最新的值刷新到主內存中;而在讀操作時,會加入一條load指令,即強迫從主內存中讀入變量的值。但volatile不保證volatile變量的原子性。
  • 讀寫鎖的優勢?

ReentrantReadWriteLock:讀寫各用一把鎖;對於讀多寫少場景效率高。

讀寫鎖表示兩個鎖,一個是讀操作相關的鎖,稱為共享鎖;另一個是寫操作相關的鎖,稱為排他鎖。我把這兩個操作理解為三句話:

1、讀和讀之間不互斥,因為讀操作不會有線程安全問題

2、寫和寫之間互斥,避免一個寫操作影響另外一個寫操作,引發線程安全問題

3、讀和寫之間互斥,避免讀操作的時候寫操作修改了內容,引發線程安全問題

總結起來就是,多個Thread可以同時進行讀取操作,但是同一時刻只允許一個Thread進行寫入操作。

  • ThreadLocal的作用?

作用:ThreadLocal的作用是為每個線程創建一個變量副本,每個線程可以修改自己所擁有的變量副本,而不會影響其他線程的副本,從而實現線程安全。 實現方式:ThreadLocal內置一個map,key表示當前線程,value是對應的值,實現線程變量私有化。 比較:ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個線程對相同資源的並發訪問,是為了多個線程之間進行通信的有效方式;而ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量)。
  • Java內存模型?

PS:JVM內存模型和JMM(Java內存模型)沒有關系。JMM的目的是為了解決Java多線程對共享數據的讀寫一致性問題。

所有線程共享主內存 每個線程有自己的工作內存 refreshing local memory to/from main memory must comply to JMM rules 每個線程都有自己的執行空間(即工作內存),線程執行的時候用到某變量,首先要將變量從主內存拷貝的自己的工作內存空間,然後對變量進行操作:讀取,修改,賦值等,這些均在工作內存完成,操作完成後再將變量寫回主內存; 工作內存可類比高速緩存,為了獲得更好的執行性能。
  • Concurren下的類?

Concurrent包下的類包括: 並發集合類:ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、ArrayBlockingQueue、LinkedBlockingQueue; 原子類:AtomicInteger 線程池:ThreadPoolExecutor、Executor;

面經總結:多線程