面經總結:多線程
-
多線程的實現?
三種方法: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區別?
-
volatile的作用?
在當前的Java內存模型下,線程可以把變量保存在本地內存(比如機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續使用它在寄存器中的變量值的拷貝,造成數據的不一致。
要解決這個問題,只需要像在本程序中的這樣,把該變量聲明為volatile(不穩定的)即可,這就指示JVM,這個變量是不穩定的,每次使用它都到主存中進行讀取。一般說來,多任務環境下各任務間共享的標誌都應該加volatile修飾。
Volatile修飾的成員變量在每次被線程訪問時,都強迫從共享內存中重讀該成員變量的值。而且,當成員變量發生變化時,強迫線程將變化值回寫到共享內存。這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。
使用volatile關鍵字修飾變量,線程要訪問變量時都是從內存中讀取,而不是從緩存當中讀取,因此每個線程訪問到的變量值都是一樣的。
-
synchronized和lock的區別?
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的作用?
-
Java內存模型?
PS:JVM內存模型和JMM(Java內存模型)沒有關系。JMM的目的是為了解決Java多線程對共享數據的讀寫一致性問題。
所有線程共享主內存 每個線程有自己的工作內存 refreshing local memory to/from main memory must comply to JMM rules 每個線程都有自己的執行空間(即工作內存),線程執行的時候用到某變量,首先要將變量從主內存拷貝的自己的工作內存空間,然後對變量進行操作:讀取,修改,賦值等,這些均在工作內存完成,操作完成後再將變量寫回主內存; 工作內存可類比高速緩存,為了獲得更好的執行性能。-
Concurren下的類?
面經總結:多線程