1. 程式人生 > 其它 >從零開始學Java-Day17

從零開始學Java-Day17

多執行緒程式設計的兩種實現方式

  1. extends Thread
    • 優點:
    • 缺點:後續變化小,侷限性大
  2. implement Runnable
    • 優點:多實現,更加靈活且解耦
    • 缺點:寫法相對複雜,一些資源需要藉助Thread

多執行緒資料安全隱患

  1. 怎麼產生?執行緒的隨機性+訪問延遲
  2. 以後如何判斷程式有沒有執行緒安全問題

在多執行緒程式中 + 有共享資料 + 多條語句操作共享資料

  • 單執行緒程式不會出現多執行緒搶佔資源的情況
  • 如果沒有共享資料,互不干涉,也不會出現資料安全問題
  • 多條語句操作了共享資料,而多條語句執行是需要時間的,存在延遲所以這個時間差導致了資料的安全問題
package cn.tedu.tickets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestRunnableV2 {
    public static void main(String[] args) {
        TicketRunnable target = new TicketRunnable();
//        Thread t1 = new Thread(target, "黃金船");
//        Thread t2 = new Thread(target, "目白麥昆");
//        Thread t3 = new Thread(target, "東海帝王");
//        Thread t4 = new Thread(target, "小慄帽");
//        t1.start();
//        t2.start();
//        t3.start();
//        t4.start();
        //執行緒池ExecutorService:用於儲存執行緒的池子,把新建/啟動/關閉執行緒都交給池來做
        //Executors:用於建立執行緒池的工具類,newFixedThreadPool()
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 1; i <= 5; i++){
            pool.execute(target);
        }
        pool.shutdown();
    }
}


class TicketRunnable implements Runnable{
    static int tickets = 100;
    Object object = new Object();
    @Override
    public void run() {
        while (true){
            /*鎖物件必須唯一*/
            /*
            如果一個方法中所有程式碼均需要被同步,那麼可以用 synchronized 修飾
            被synchronized關鍵字修飾的方法稱為同步方法
             */
            synchronized (object){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tickets > 0){
                    System.out.println(Thread.currentThread().getName() + "賣了第" + tickets-- + "張票" );
                }
                if (tickets <= 0){
                    break;
                }
            }
        }
    }
}

多執行緒安全問題解決

加鎖:注意

  1. 加鎖的位置

同步|亦步

同步:類似於排隊效果

  • 優點:執行緒安全
  • 缺點:效率低

非同步:不排隊,多執行緒效果,各執行緒都搶佔資源

  • 優點:效率高
  • 缺點:執行緒不安全

雙重校驗機制

  1. 增加判斷控制--有票的時候在賣票

  2. 同步程式碼塊,主要強調同步,同一時刻同一資源只能被一個執行緒物件

  3. sychronized(鎖物件){可能會發生安全問題的程式碼}

    • 同步程式碼塊使用的鎖物件必須唯一
package cn.tedu.tickets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestRunnableV2 {
    public static void main(String[] args) {
        TicketRunnable target = new TicketRunnable();
//        Thread t1 = new Thread(target, "黃金船");
//        Thread t2 = new Thread(target, "目白麥昆");
//        Thread t3 = new Thread(target, "東海帝王");
//        Thread t4 = new Thread(target, "小慄帽");
//        t1.start();
//        t2.start();
//        t3.start();
//        t4.start();
        //執行緒池ExecutorService:用於儲存執行緒的池子,把新建/啟動/關閉執行緒都交給池來做
        //Executors:用於建立執行緒池的工具類,newFixedThreadPool()
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 1; i <= 5; i++){
            pool.execute(target);
        }
        pool.shutdown();
    }
}


class TicketRunnable implements Runnable{
    static int tickets = 100;
    Object object = new Object();
    @Override
    public void run() {
        while (true){
            /*鎖物件必須唯一*/
            /*
            如果一個方法中所有程式碼均需要被同步,那麼可以用 synchronized 修飾
            被synchronized關鍵字修飾的方法稱為同步方法
             */
            synchronized (object){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tickets > 0){
                    System.out.println(Thread.currentThread().getName() + "賣了第" + tickets-- + "張票" );
                }
                if (tickets <= 0){
                    break;
                }
            }
        }
    }
}
package cn.tedu.tickets;
//本類用於解決繼承下多執行緒售票案例的資料安全問題
public class TestExtendV2 {
    public static void main(String[] args) {
        TicketThreadV2 t1 = new TicketThreadV2("小林");
        TicketThreadV2 t2 = new TicketThreadV2("托爾");
        TicketThreadV2 t3 = new TicketThreadV2("法夫涅爾");
        TicketThreadV2 t4 = new TicketThreadV2("康納");
//        t1.setName("小林");
//        t2.setName("托爾");
//        t3.setName("法夫涅爾");
//        t4.setName("康納");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class TicketThreadV2 extends Thread{
    private static int tickets = 100;

    public TicketThreadV2() {
    }

    public TicketThreadV2(String name) {
        setName(name);
    }

    @Override
    public void run() {
        while (true){
            /*
             * 雙重校驗二:使用同步程式碼塊
             * synchronized(鎖物件){容易發生資料安全問題的程式碼}
             * 在同步程式碼塊中,同一時刻,同一資源只能被一個執行緒獨享,排隊
             * 注意:鎖物件必須唯一,如果不唯一,還會發生安全問題
             * 如果是繼承的方式,鎖物件一般使用本類的位元組碼物件
             */
            synchronized (TicketThreadV2.class){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tickets <= 0){
                    break;
                }
                if (tickets == 1){
                    System.out.println("票賣完了");
                }
                //雙重校驗一
                if (tickets > 0){
                    System.out.println(getName() + "買到第" + tickets-- + "張票");
                }

            }
        }

    }
}

ExecutorService/Executors

ExecutorService:用來儲存執行緒的池子,把新建執行緒/啟動執行緒/關閉執行緒的任務都交給池來管理

  • execute(Runnable任務物件) 把任務丟到執行緒池

Executors 輔助建立執行緒池的工具類

  • newFixedThreadPool(int nThreads) 最多n個執行緒的執行緒池
  • newCachedThreadPool() 足夠多的執行緒,使任務不必等待
  • newSingleThreadExecutor() 只有一個執行緒的執行緒池

執行緒池

ExecutorService:用來儲存執行緒的池子,把新建執行緒/啟動執行緒/關閉執行緒的任務都交給池來管理

建立執行緒池的工具類:Executors.newFixedThreadPool(int 自定義執行緒數)

啟動執行緒池中的執行緒:pool.execute(target目標業務物件)

執行緒池會自動管理執行緒,執行緒池目前單機測試不關閉,需要手動關閉

執行緒鎖

悲觀鎖(sychronized):也叫互斥鎖

樂觀鎖

設計模式

單例模式可以說是大多數開發人員在實際中使用最多的,常見的Spring預設建立的bean就是單例模式的。
單例模式有很多好處,比如可節約系統記憶體空間,控制資源的使用。
其中單例模式最重要的是確保物件只有一個。
簡單來說,保證一個類在記憶體中的物件就一個。

單例設計模式:

  1. 建立一個私有化靜態方法
  2. 私有化的構造方法不讓外部呼叫
  3. 通過自定義的靜態方法獲取例項

實現思路:

  1. 構造方法私有化--為了防止外部直接呼叫本類構造方法
  2. 本類呼叫構造方法建立私有物件--物件私有化是為了不讓外界直接獲取
  3. 提供公共的全域性訪問點--為了讓外界按指定的方式獲取物件
package cn.likou.demo06;

public class Test02 {
    private Test02(){}
    static private Test02 t = new Test02();
    public static Test02 go(){
        return t;
    }

    public static void main(String[] args) {
        Test02 t1 = Test02.go();
        Test02 t2 = Test02.go();
        System.out.println(t1);
        System.out.println(t2);
        System.out.println(t1 == t2);

        MySingle s1 = MySingle.go();
        MySingle s2 = MySingle.go();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

class MySingle{
    private MySingle(){}
    static private MySingle mySingle;

    public static MySingle go(){
        if (mySingle == null){
            mySingle = new MySingle();
        }
        return mySingle;
    }
}