1. 程式人生 > >十、多線程基礎-需要強化的知識點

十、多線程基礎-需要強化的知識點

理解 調用 釋放 原子 通知 ted sta 恢復 println

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執行完後執行  
     * 
*/ 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); } } }
View Code

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鎖對象時,會出現阻塞。


十、多線程基礎-需要強化的知識點