開啓新線程的方法and解決線程安全問題的方法

併發與並行

併發:兩個或多個事件(線程)在同一時間段內發生(一個Cpu交替執行)

並行:兩個或多個事件(線程)在同一時刻發生

線程與進程

進程:是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創建、運行到消亡的過程。

進入到內存的程序就是進程

線程:能獨立運行的基本單位,也是獨立調度和分派的基本單位,  不具備獨立的內存空間

 

進程和線程的區別

  1. 地址空間:進程是資源分配的基本單位線程與資源分配無關,它屬於某個進程,與其它線程一起共享此進程的地址空間
  2. 切換和調度:線程上下文切換比進程上下文切換要快得多
  3. 在多線程OS中,進程不是一個可執行的實體。

 

線程調度

  • 分時調度:所有線程輪流使用cpu的使用權,平均分配每個線程使用時間
  • 搶佔式調度:優先讓優先級高的線程使用CPU,如果優先級相同,則隨機分配,Java則是搶佔式調度

主線程:執行main方法的線程

main方法執行過程:JVM執行main方法,首先將main方法放入棧內存中,開闢一條main通向CPU的路

                                 徑,這個路徑就叫做main(主)線程。

創建多線程

第一種方式:

1、創建一個Thread的子類

2、重寫Thread的run() 方法,run中寫的是新線程執行的任務

3、創建一個Thread的子類對象

4、main中調用該對象的start方法

第二種方式:

1、創建一個實現類實現Runnable接口,實現類中實現run()方法,設置線程任務

2、創建一個實現類對象

3、創建一個thread對象,構造方法中傳遞Runnable實現類對象名稱

4、利用Thread對象調用start()方法,開啓新線程執行run()方法

線程多次啓動是違法的,線程結束執行結束後,不能再重新啓動

兩種方法的區別(Runnable的優點)

1、Java不允許多繼承,使用Runnable實現後,還可以繼承其它類

2、方便資源共享(共享一個實例對象),將設置線程任務和開啓新線程進行了分離(解耦),增強了程序的可擴展性,

public class SnapUpThread implements Runnable {
    private int ticket = 100;
    static int i = 0;
    String name = "用戶";

    @Override
    public void run() {        //設置線程任務
        i++;
        int nameNumber = i;
        while(true){
            if (ticket <= 0) {
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + nameNumber + "搶到票:" + ticket--);
        }

    }
}
public static void main(String[] args) {
        SnapUpThread runnable = new SnapUpThread();
        new Thread(runnable).start();        //開啓新線程
        new Thread(runnable).start();        //開啓新線程
    }

結果:

 

多線程的原理

       當JVM執行main方法時,開闢了一條從main方法通向CPU的路徑(線程),當執行到Thread th = new newThread();時,由再開闢一條從th通向CPU的路徑,執行到start()方法時便開始執行th線程的run方法。

       對於CPU而言,便有了選擇權,隨機選擇線程執行,這就有了程序的隨機打印結果。同樣也可以認爲,兩個線程同時搶奪CPU的執行權(執行時間)。

 

Thread常用方法

  • 獲取線程名稱兩種方式:通過對象名來獲取線程名稱(對象.getName()),或者通過Thread的靜態方法獲取當前線程Thread.currentThread().getName()  
  • 設置線程名稱兩種方式:通過對象名來獲取線程名稱(對象.setName()),或者在新線程中寫一個帶參name構造方法,調用父類構造,並傳入name參數
  • 暫停:Thread.sleep(xxx毫秒);使當前線程暫停xxx秒

 

線程安全問題

 

解決辦法

       在賣票時(操作共享數據),即使失去cpu控制權,也不允許其它線程來操作共享數據。

       當一個線程1搶到cpu使用權後,執行到synchronized語句時,獲得了唯一的鎖對象,因此就算失去cpu使用權,別的線程因爲沒有得到鎖對象,會一直在synchronized語句阻塞,直到線程1釋放出鎖對象。

1、同步代碼塊實現

synchronized(鎖對象){

//操作共享數據的語句

}

注意:

  • 鎖對象可以是任意類型對象
  • 多個線程中的鎖對象必須是同一個
  • 靜態方法中的鎖對象比較特殊,是本類的class屬性-->class文件對象

2、同步方法實現

把操作共享數據的語句封裝成一個方法,方法修飾符後增加synchronized

(同步方法的鎖對象其實就是創建的Runnable實現類對象,所以在第一個方法中也可以使用this作爲鎖對象傳入)

public synchronized int payTicket(){
   if (ticket <= 0) {
        return 0;
   }
   System.out.println(name + nameNumber + "搶到票:" + ticket);
   ticket--;
   return 1;
}
int a = 1;
while(a == 1){
    a = payTicket();
}

3、使用Java.util.concurrent.locks.ReentrantLock

1、創建ReentrantLock rt = new ReentrantLock();

2、在操作共享數據的語句前加鎖:rt.lock();

3、在操作完共享數據的語句後放掉鎖:rt.unlock();

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章