【Java】【基礎篇】day11:多線程

前言

本期任務:畢向東老師Java視頻教程學習筆記(共計25天)


代碼

/*
java多線程的幾種實現方式
*/

public class ThreadTest {
    public static void main(String[] args) {
        // 新寫法
        new Thread(() -> {
            for (int x = 0; x < 200; x++) {
                System.out.println(Thread.currentThread().getName() + "......" + x);
            }
        }).start();

        /*
        創建線程的第一種方式是:繼承Thread類。原因:要覆蓋run方法,定義線程要運行的代碼。
        步驟:
            1,繼承Thread類。
            2,覆蓋run方法。將線程要運行的代碼定義其中。
            3,創建Thread類的子類對象,其實就是在創建線程,調用start方法。
        */

        // 舊寫法
        new Test1().start();


        // 匿名內部類
        new Thread() {
            public void run() {
                for (int x = 0; x < 200; x++) {
                    System.out.println(Thread.currentThread().getName() + "......" + x);
                }
            }
        }.start();


        // 實現Runable接口
        /*

        創建線程的第二種方式:實現Runnable接口。

        步驟:
            1,定義了實現Runnable接口。
            2,覆蓋接口的run方法。將多線程要運行的代碼存入其中。
            3,創建Thread類的對象(創建線程),並將Runnable接口的子類對象作爲參數傳遞給Thread的構造函數。
                爲什麼要傳遞?因爲線程要運行的代碼都在Runnable子類的run方法中存儲。所以要將該run方法所屬的對象
                傳遞給Thread。讓Thread線程去使用該對象調用其run方法。
            4,調用Thread對象的start方法。開啓線程。

        */

        Runnable r = new Runnable() {
            public void run() {
                for (int x = 0; x < 200; x++) {
                    System.out.println(Thread.currentThread().getName() + "......" + x);
                }
            }
        };
        new Thread(r).start();



        // 主線程運行
        for (int x = 0; x < 1000; x++) {
            System.out.println(Thread.currentThread().getName() + "......" + x);
        }

    }
}

class Test1 extends Thread {
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(Thread.currentThread().getName() + "......" + x);
        }
    }
}
/*
需求:簡單的賣票程序
多個窗口同時賣票

創建線程的第一種方式:繼承Thread類
步驟:
1. 定義類繼承Thread
2. 複寫Thread類中的run方法
3. 調用線程的start方法
    該方法兩個作用,啓動線程,調用run方法

創建線程的第二種方式:實現Runable接口
步驟:
1. 定義類實現Runable接口
2. 覆蓋Runable接口中的run方法
    將線程要運行的代碼放在該run方法中

3. 通過Thread類建立線程對象
4. 將Runable接口的子類對象作爲實際參數傳遞給Thread類的構造函數
    爲什麼要將Runable接口的子類對象傳遞給Thread的構造函數:
    因爲,自定義的run方法所屬的對象是Runable接口的子類對象
    所以要讓線程去指定隊形的run方法,就必須明確該run方法所屬對象

5. 調用Thread類的start方法開啓線程並調用Runable接口子類的run方法

實現方式與繼承方式有什麼區別呢?
    實現方式好處:避免了單繼承的侷限性
    在定義線程時,建議使用實現方式

兩種方式區別:
    繼承Thread:線程代碼存放在Thread子類的run方法中
    實現Runable:線程代碼存放在接口的子類的run方法中


使用Thread.sleep()製造線程不安全,使用這個方法,會拋出InterruptException異常
由於存在線程10ms的休眠時間,使得出現tick爲0,甚至-1、-2的錯票情形,多線程運行存在安全問題

問題原因:
    當多條語句在操作同一個線程共享數據的時候,一個線程對多條語句只執行了一部分,還沒執行完,
    另一個線程參與進來執行,導致共享數據出現錯誤。

解決方法:
    對多條操作共享數據的語句,只能讓一個線程都執行完。在執行過程中,其他線程不可以參與執行。


Java對於多線程的安全問題提供了專業的解決方式

就是同步代碼塊

synchronized(對象){

    需要被同步的代碼

}

對象如同鎖,持有鎖的線程可以在同步中執行。
沒有持有鎖的線程即使獲取cpu的執行權,也進不去,因爲沒有獲取鎖

火車上的衛生間 --- 經典。

同步的前提:
1. 必須要有兩個或兩個以上的線程。
2. 必須是多個線程使用同一個鎖
3. 必須保證同步中之惡能有一個線程在運行。

好處:解決了線程安全問題
弊端:多個線程需要判斷鎖,較爲消耗資源

同步函數也是可以解決線程安全的,直接在函數中加入synchronized限制符

同步函數用的是哪個鎖呢?
函數需要被對象調用,那麼函數都有一個所屬對象引用,就是this
所以同步函數使用的鎖是this

通過程序進行驗證
使用兩個線程來買票
一個線程在同步代碼塊中
一個線程在同步函數中
都在執行買票動作

如果同步函數被靜態修飾後,使用的鎖是什麼呢?
通過驗證,發現不是this,因爲靜態方法中不可以定義this
靜態進內存時,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象。
類名.class 該對象的類型是class

靜態的同步方法,使用的鎖是該方法所在類的字節碼文件對象:類名.class


*/

// 實現Runable
class Ticket implements Runnable {
    private static int tick = 1000;
    Object obj = new Object();

    // 同步函數
    public synchronized void show() {
        if (tick > 0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName() + ":" + tick--);
        }
    }

    public void run1() {
        while (true)
            show();
    }

    // 同步代碼塊
    public void run() {

        while (true) {
            synchronized (this) {
                if (tick > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (Exception e) {
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + tick--);
                }
            }
        }
    }
}


// 繼承Thread
class Ticket1 extends Thread {
    private static int tick = 100;

    public void run() {
        while (tick > 0) {
            System.out.println(Thread.currentThread().getName() + ":" + tick--);
        }
    }
}


public class TicketDemo {
    public static void main(String[] args) {
        // 實現Runable
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);

        t1.start();
        t2.start();
        t3.start();
        t4.start();


        /*
        // 繼承Thread
        Ticket1 t1 = new Ticket1();
        Ticket1 t2 = new Ticket1();
        Ticket1 t3 = new Ticket1();
        Ticket1 t4 = new Ticket1();

        t1.start();
        t2.start();
        t3.start();
        t4.start();

        */
    }


}

/*
單例設計模式

1. 餓漢式
class Single{
    private static final Single s = new Single();
    private Single(){}
    public static Single getInstance(){
        return s;
    }
}

2. 懶漢式
    延遲加載,
    多線程訪問時會出現線程安全問題,
    可使用同步鎖解決, 同步函數和同步代碼塊都可,
    使用雙重判斷可解決效率問題,
    加同步的使用,使用的鎖是該類所屬的字節碼文件對象

*/


class Single {
    private static Single s = null;

    private Single() {
    }

    public static Single getInstance() {
        if (s == null) {
            synchronized (Single.class) {
                if (s == null) {
                    s = new Single();
                }
            }

        }
        return s;
    }
}
/*
死鎖
同步中嵌套同步。

以下爲死鎖的一個實例

*/

class Test implements Runnable {
    private boolean flag;

    Test(boolean flag) {
        this.flag = flag;
    }

    public void run() {
        if (flag) {
            synchronized (MyLock.locka) {
                System.out.println("if locka");
                synchronized (MyLock.lockb) {
                    System.out.println("if lockb");
                }
            }
        } else {
            synchronized (MyLock.lockb) {
                System.out.println("else lockb");
                synchronized (MyLock.locka) {
                    System.out.println("else locka");
                }
            }
        }
    }
}

class MyLock {
    static Object locka = new Object();
    static Object lockb = new Object();
}


public class DeadLockDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Test(true));
        Thread t2 = new Thread(new Test(false));
        t1.start();
        t2.start();
    }
}

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