Java多線程學習筆記(一)- 基本概念

一、基本概念

1、併發與並行:

   1)併發:多個任務交替執行。

   2)並行:多個任務同時執行。如果系統只有一個CPU,那真實環境中不可能是真實並行,因爲一個CPU一次只能執行一個指令。多核CPU有可能出現並行。
 

2、臨界區:
     一種公共資源或共享數據,每一次只能有一個線程使用它。一旦臨界區資源被佔用,其他線程要想使用這個資源,就必須等待。
 

3、阻塞(Blocking)和 非阻塞(Non-Blocking):

     阻塞:用來形容多線程間的相互影響。一個線程佔用了臨界區,其他需要使用這個臨界區的線程就必須等待,等待會導致線程掛起,這種情況就是阻塞。

     非阻塞:沒有一個線程可以防礙其它線程執行。
 

4、死鎖(DeadLock)、飢餓(Starvation)和活鎖(LiveLock):

    死鎖: 各線程彼此阻塞。

    飢餓:某個或多個線程由於某種原因(線程優先級太低,或某個線程一直佔用資源)無法獲得需要的資源,導致一直無法執行。

   活鎖:兩個線程相互謙讓資源,導致沒有一個線程可以同時拿到所有資源而正常執行。


5、併發級別:

    根據控制併發的策略,可以發併發級別分爲: 阻塞、無飢餓、無障礙、無鎖、無等待。   
 

6、多線程的原子性、可見性、有序性:

    a、 原子性( Atomicity ):指一個操作是不可中斷的。即使是在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程干擾。

           如: 對於32位系統,Long (64位)型數據的讀寫不是原子性的,

     b、可見性:當一個線程修改了某一個共享變量的值,其他線程是否能夠立即知道這個修改。

           如: 多核CPU中,CPU1 將變量 t 緩存在Cache或寄存器, 如果CPU2上的某線程修改了t 的值,則CPU1 不會知道,這就是可見性的問題。

           可見性問題有多種情況: 緩存優化,硬件優化,指令重排,編輯器優化。

     c、有序性:指令重排後,代碼執行順序會變化。

 

7、線程中斷的三種方法:

1)  Thread.interrupt();  // 中斷線程

實例方法,通知目標線程中斷,即設置中斷標誌位。中斷標誌位表示當前線程已經被中斷了。
 

2)  Thread.isInterrupted();  // 判斷是否被中斷

實例方法,判斷當前線程是否有被中斷(通過檢查中斷標誌位)。

 

3) Thread.interrupted();  // 判斷是否被中斷,並清除當前中斷狀態。

也用來判斷當前線程的中斷狀態,但同時會清除當前線程的中斷標識位狀態。

 

8、Thread.sleep()方法:

  當線程在 sleep()休眠時,如果被中斷,則會拋出InterruptException中斷異常

該異常不是運行時異常,程序必須捕獲並處理它。
 

9、Object.wait()  方法:

當在一個對象實例上調用 wait()方法後,當前線程就會在這個對象上等待,直到其它線程調用了obj.notify()方法爲止。

 obj對象成爲多個線程之間的通信手段。

Object.wait()方法必須包含在對應的 synchronized語句中。

執行前需要獲得目標對象的監視器 - 用synchronized 語句

wait() 和 sleep()方法的區別:
1)、相同點: 都可以讓線程等待,

2)、不同點: wati 可以被喚醒,會釋放目標對象的鎖,slee()方法不會釋放任何資源。

 

10、Object.notify()方法:隨機喚醒(不公平)等待的線程。

         Object.notifyAll()方法:喚醒全部等待隊列中的線程。

執行前需要獲得目標對象的監視器


11、suspend( 掛起)和 resume (繼續執行)線程:

這是兩個相反的操作,已經不建議使用。

因爲: suspend()在掛起線程時,不釋放任何的鎖資源,直至對應的線程執行了resume()操作。

但如果 resume()操作先與 suspend()執行,則被掛起的線程在很難有機會被繼續執行。

嚴重時它佔用的鎖不被釋放,會導致整個系統工作不正常。

 

12、Join (等待線程結束) 和 yield ( 謙讓 )  方法:

 Join 方法有兩個:

a、 public final void join(): 無限等待,一直阻塞當前線程,直到目標線程執行完畢。

b、 public final synchronized void join( long millis ):給定一個最大等待時間,如超過
等待時間目標線程還在執行,
當前線程會不再等待而繼續往下執行。

join()的核心代碼是 wait(0);

注:不要在應用程序中,在Thread對象實例上使用類似 wait()或 notify()等方法,
因爲這很可能會影響系統API的工作
或者被系統API所影響(爲什麼? 沒有理解)

 

yield()方法定義: public static native void yield();

靜態方法,一旦執行,它會使當前線程讓出CPU。但後續還會進行CPU資源的爭奪。

如果一個線程不太重要或者優先級低,可以在適當的時候調用 Thread.yield().

 

13、Java內存模型:原子性、有序性、可見性。


14、volatile: 當用該關鍵詞聲明變量後,虛擬機會特別小心地處理,保證該變量的可見性。
但 volatile 不能代替鎖,也無法保證一些複合操作的原子性。

 

15、線程組:

ThreadGroup tg = new ThreadGroup("TGroup");
Thread t1 = new Thread(tg, new ThreadGroupName(), "T1");
Thread t2 = new Thread(tg, new ThreadGroupName(), "T2");

tg.activeCount()

tg.list();

 

16、守護線程( Daemon),與之對應的是用戶線程。

Thread t = new DaemonT();
t.setDaemon(true);  //必須在 start()方法前設置。
t.start();

 

17、線程優先級:

java中,線程優先級從1到10。

高優先級的線程競爭資源時會更有優勢,但並不絕對。

 

18、線程安全的概念:

1)、synchronized 的基本用法:

   a、指定加鎖對象:對給定對象加鎖,進入同步代碼前要獲得給定對象的鎖。

     如: Object instance = new Object();

             synchronized( instance )

             {
             ...
             }

           

            main()

            {
                Thread t1 = new Thread(instance);

                Thread t2 = new Thread(instance);
                t1.start(); t2.start();
               ....

            }

 

   b、直接作用於實例方法:相當於對當前實例加鎖,進入同步代碼前要獲得當前實例的鎖。

      如: publicsynchronized void increase()

             {
              ...
             }

             main()

            {
                Thread t1 = new Thread(instance);

                Thread t2 = new Thread(instance);
                t1.start(); t2.start();
               ....

            }

   c、直接作用於靜態方法:相當於對當前類加鎖,進入同步代碼前要獲得當前類的鎖。

      如:public staticsynchronized void increase()

             {...}

             main()

            {
                Thread t1 = new Thread(new
StaticSyncInstatnce());

                Thread t2 = new Thread(newStaticSyncInstatnce());
                t1.start(); t2.start();
               ....

            }

 

問題: 這三種用法是鎖的粒度範圍不同?a和b 有什麼區別?

 

 

二、線程池

1、工作方式:

      有一個隊列,任務被提交到這個隊列中(也可有多個隊列)。一定數量的線程會從該隊列中取任務,

      然後執行。執行完任務後,線程會從任務隊列中取下一個任務並執行,如果沒有任務要執行,則等待。

 2、線程池有最小線程數最大線程數,池中有最小數目的線程隨時待命,等待任務指派給他們。

      最小線程數也叫核心池大小

      最大線程數是一個限流閥,防止一次執行太多線程。最大線程數的大小取決於負載特性以及底層硬件

     最優線程數還與每個任務的阻塞頻率有關。

 

3、線程池Executor框架:

ThreadPoolExecutor

Executors 爲線程池工廠,包含如下構造線程池的方法:

public staticExecutorServicenewFixedThreadPool() :

   返回一個固定線程數量的線程池。

 

public staticExecutorService newSingleThreadExecutor:

    返回只有一個線程的線程池。


public static ExecutorService newCachedThreadPool

  返回一個可根據實際情況調整線程數量的線程池。線程池的線程數量不固定。
 

public staticScheduledExecutorServicenewSingleThreadScheduledExecutor

   返回一個 ScheduledExecutorService 對象,線程池大小爲一。

   ScheduledExecutorService接口在ExecutorService 接口只上擴展了在給定時間執行某任務的功能。
    如在某個固定時間執行某任務的功能,如在某個固定的延遲之後執行,或者週期性的執行某個任務。
 

public staticScheduledExecutorService  newScheduledThreadPool

   該方法也返回一個ScheduledExecutorService對象,但該線程可以指定線程數量。

public staticExecutorService  unconfigurableExecutorService

public static ScheduledExecutorService  unconfigurableScheduledExecutorService

 

20、JDK 併發容器:

CuncurrentHashMap:

CopyOnWriteArrayList:

ConcurrentLinkedQueue:

BlockingQueue:

ConcurrentSkipListMap:

 

三、Java線程調試工具

1、jps - 顯示當前系統中所有的java 進程。

2、jstack 打印給定 Java 進程的內部線程及堆棧。

3、javap [不帶class後綴的類名]-  反編譯java 代碼

問題

1、ActomicLong 類?

2、JUC(包括重入鎖(Lock)、線程池/連接池(commom-pool)、Timer、Future模式、AQS、CAS等)

3、代理,反射

 

    

 

   

發佈了80 篇原創文章 · 獲贊 10 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章