十、多線程基礎-需要強化的知識點
1、sleep()和wait()方法異同
sleep方法和wait方法都可以用來放棄CPU一定的時間,不同點在於如果線程持有某個對象的監視器,sleep方法不會放棄這個對象的監視器,wait方法會放棄這個對象的監視器
1)Thread.sleep():方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。在調用sleep()方法的過程中,線程不會釋放對象鎖。
2)Object.wait():線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備獲取對象鎖進入運行狀態
2、start()和run()方法區別
start()方法:
1)用start方法來啟動線程,真正實現了多線程運行,這時無需等待run方法體代碼執行完畢而直接繼續執行下面的代碼。
2)通過調用Thread類的start()方法來啟動一個線程,這時此線程處於就緒(可運行)狀態,並沒有運行,一旦得到CPU時間片,就開始執行run()方法。
run()方法:
1)run()方法只是類的一個普通方法而已,如果直接調用Run方法,程序中依然只有主線程這一個線程,其程序執行路徑還是只有一條。
總結:
1)調用start方法方可啟動線程,
2)而run方法只是thread的一個普通方法調用,還是在主線程裏執行。
3)把需要並行處理的代碼放在run()方法中,start()方法啟動線程將自動調用run()方法,這是由jvm的內存機制規定的。
4) 並且run()方法必須是public訪問權限,返回值類型為void.
3、wait()和notify()/notifyAll()方法為什麽要在同步塊中被調用
這是JDK強制的,wait()方法和notify()/notifyAll()方法在調用前都必須先獲得對象的鎖
4、wait()和notify()/notifyAll()方法在放棄對象監視器時區別是什麽
wait()方法立即釋放對象監視器,notify()/notifyAll()方法則會等待線程剩余代碼執行完畢才會放棄對象監視器 。
5、線程的join、yield、priority用法
1)thread1.join();可以將thread1理解為插隊者,當thread1執行完畢之後當前線程才會繼續執行
public class JoinThreadDemo02 { /* * T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行 *View Code*/ public static void main(String[] args) { final Thread t1 = new Thread(new Runnable() { public void run() { for (int i = 0; i < 20; i++) { System.out.println("t1,i:" + i); } } }); final Thread t2 = new Thread(new Runnable() { public void run() { try { t1.join(); } catch (Exception e) { // TODO: handle exception } for (int i = 0; i < 20; i++) { System.out.println("t2,i:" + i); } } }); Thread t3 = new Thread(new Runnable() { public void run() { try { t2.join(); } catch (Exception e) { // TODO: handle exception } for (int i = 0; i < 20; i++) { System.out.println("t3,i:" + i); } } }); t1.start(); t2.start(); t3.start(); } } package threadLearning.join_yield_priority; class JoinThread implements Runnable { public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "---i:" + i); } } }
2)現代操作系統基本采用時分的形式調度運行的線程,線程分配得到的時間片的多少決定了線程使用處理器資源的多少,也對應了線程優先級這個概念。在JAVA線程中,通過一個int priority來控制優先級,範圍為1-10,其中10最高,默認值為5。下面是源碼(基於1.8)中關於priority的一些量和方法。
t1.setPriority(10) // 註意設置了優先級, 不代表每次都一定會被執行。 只是CPU調度會優先分配
3)Thread.yield()方法的作用:暫停當前正在執行的線程,並執行其他線程。(可能沒有效果)yield()讓當前正在運行的線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行的機會。因此,使用yield()的目的是讓具有相同優先級的線程之間能夠適當的輪換執行。但是,實際中無法保證yield()達到讓步的目的,因為,讓步的線程可能被線程調度程序再次選中。結論:大多數情況下,yield()將導致線程從運行狀態轉到可運行狀態,但有可能沒有效果。
6、synchronized和ReentrantLock的區別
synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那麽它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:
1)ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖
2)ReentrantLock可以獲取各種鎖的信息
3)ReentrantLock可以靈活地實現多路通知
另外,二者的鎖機制其實也是不一樣的。ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word,這點我不能確定。
7、volatile關鍵字 (示列2個)
理解volatile關鍵字的作用的前提是要理解Java內存模型, volatile關鍵字的作用主要有兩個:
1)多線程主要圍繞可見性和原子性兩個特性而展開,使用volatile關鍵字修飾的變量,保證了其在多線程之間的可見性,即每次讀取到volatile變量,一定是最新的數據
2)代碼底層執行不像我們看到的高級語言—-Java程序這麽簡單,它的執行是 Java代碼–>字節碼–>根據字節碼執行對應的C/C++代碼–>C/C++代碼被編譯成匯編語言–>和硬件電路交互 ,現實中,為了獲取更好的性能JVM可能會對指令進行重排序,多線程下可能會出現一些意想不到的問題。使用volatile則會對禁止語義重排序,當然這也一定程度上降低了代碼執行效率從實踐角度而言,volatile的一個重要作用就是和CAS結合,保證了原子性,詳細的可以參見java.util.concurrent.atomic包下的類,比如AtomicInteger。
普通變量在多線程中的不可見性示列:
/** * * @classDesc: 功能描述:Volatile 關鍵字的作用是變量在多個線程之間可見。 * 如果變量沒有使用volatile關鍵字,那麽變量在多線程之間是不可見的,線程讀取的是該變量的副本,而不會從主內存中讀取; * @author: zjb * @createTime: 創建時間:2018-6-24 下午4:57:55 * @version: v1.0 * @copyright: */ public class ThreadVolatileDemo extends Thread{ //public volatile boolean flag=true; public boolean flag=true; @Override public void run(){ System.out.println("開始執行子線程...."+Thread.currentThread().getName()); while (flag) { } System.out.println("線程停止"+Thread.currentThread().getName()); } public void setRuning(boolean flag) { this.flag = flag; } } package threadLearning.volatileKeyWord; public class ThreadVolatileDemoTest { public static void main(String[] args) throws InterruptedException { ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo(); threadVolatileDemo.start(); Thread.sleep(1000); threadVolatileDemo.setRuning(false); System.out.println("flag 已經設置成false"); Thread.sleep(1000); System.out.println("threadVolatileDemo.flag---》"+threadVolatileDemo.flag); } /* 已經將結果設置為fasle為什麽?還一直在運行呢。 原因:線程之間是不可見的,讀取的是副本,沒有及時讀取到主內存結果。 解決辦法使用Volatile關鍵字將解決線程之間可見性, 強制線程每次讀取該值的時候都去“主內存”中取值 */ }View Code
AtomicInteger的原子性示例:
AtomicInteger :可以以原子方式更新的int值。
AtomicInteger用於諸如原子遞增計數器之類的應用程序,不能用作java.lang.Integer的替換。但是,這個類確實擴展了Number,允許處理基於數字的類的工具和實用程序進行統一訪問。
public class AtomicIntegerTest extends Thread { private static AtomicInteger atomicInteger = new AtomicInteger(); @Override public void run() { for (int i = 0; i < 100; i++) { //等同於i++ atomicInteger.incrementAndGet(); } System.out.println("atomicInteger----->"+atomicInteger); } public static void main(String[] args) { // 初始化10個線程 AtomicIntegerTest[] volatileNoAtomicThread = new AtomicIntegerTest[10]; for (int i = 0; i < 10; i++) { // 創建 volatileNoAtomicThread[i] = new AtomicIntegerTest(); } for (int i = 0; i < volatileNoAtomicThread.length; i++) { volatileNoAtomicThread[i].start(); } } }View Code
8、volatile與synchronized區別
1)volatile輕量級,只能修飾變量。synchronized重量級,還可修飾方法
2)volatile只能保證數據的可見性,不能用來同步,因為多個線程並發訪問volatile修飾的變量不會阻塞。使用Volatile不能保證線程的安全性(原子性)。synchronized不僅保證可見性,而且還保證原子性,因為,只有獲得了鎖的線程才能進入臨界區,從而保證臨界區中的所有語句都全部執行。多個線程爭搶synchronized鎖對象時,會出現阻塞。
十、多線程基礎-需要強化的知識點