1. 程式人生 > >java核心技術-多執行緒基礎

java核心技術-多執行緒基礎

程序、執行緒

​ 程序(Process) 是程式的執行例項。例如,一個執行的 Eclipse 就是一個程序。程序是程式向作業系統申請資源(如記憶體空間和檔案控制代碼)的基本單位。執行緒(Thread)是程序中可獨立執行的最小單位。一個程序可以包含多個執行緒。程序和執行緒的關係,好比一個營業中的飯店與其正在工作的員工之間的關係。

1.1 執行緒的建立、啟動與執行

在 Java 中實現多執行緒主要用兩種手段,一種是繼承 Thread 類,另一種就是實現 Runnable 介面。(當然還有Callable和執行緒池)。下面我們就分別來介紹這兩種方式的使用,其他請關注此部落格下文。

(1).繼承Thread的類

public class PrimeThread extends Thread{
    //執行緒執行體
    @Override
    public void run(){
        
        for(int i = 0; i < 100; i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + "=" + i);
            }
        }
        
    }
    
}
public class TestThread {
    public static void main(String[] args) {
        //新建一個執行緒
        PrimeThread p1 = new PrimeThread();
        //啟動一個執行緒
        p1.start();
        
        PrimeThread p2 = new PrimeThread();
        p2.start();
        
        
        for(int i = 0; i < 100; i++ ){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + "=" + i);
            }
        }
        
    }
}

(2).實現 Runnable介面

public class Ticket implements Runnable{
    
    private int ticket = 100;

    @Override
    public void run() {
        while(ticket > 0){
            System.out.println(Thread.currentThread().getName() + "=" + --ticket);
        }
        
    }
}
public class TestThread2 {
    public static void main(String[] args) {
        
        Ticket ticket = new Ticket();
        
        //雖然是實現了Runnable介面 本質上只是實現了執行緒執行體 啟動工作還是需要Thread類來進行
        Thread t1 = new Thread(ticket,"售票視窗一");
        t1.start();
        
        Thread t2 = new Thread(ticket,"售票視窗二");
        t2.start();
        
        Thread t3 = new Thread(ticket,"售票視窗三");
        t3.start();
    }
}

兩種實現方式的對比:

1.從面向物件程式設計角度看:第一種建立方式(繼承Thread類) 是一種基礎繼承的技術,第二種建立方式(以Runnable介面例項為構造器引數直接通過new建立Thread例項)是一種基礎組合的技術。方式二不僅會避免單繼承的尷尬,也會降低類與類之間的耦合性。

2.從物件共享角度看:第二種建立方式意味著多個執行緒例項可以共享同一個Runnable例項。而第一種方式則需要依賴static關鍵字來完成操作。

1.2 執行緒的控制

Java的排程方法

同優先順序執行緒組成先進先出佇列(先到先服務),使用時間片策略

對高優先順序,使用優先排程的搶佔式策略

Thread類的相關方法:

  • sleep(long millis) : 是 Thread 類中的靜態方法,使當前執行緒進入睡眠狀態
  • join() / join(long millis) : 是一個例項方法,使當前執行緒進入阻塞狀態
  • interrupt() : 中斷阻塞狀態的執行緒
  • isAlive() : 判斷當前執行緒是否處於存活狀態
  • yield() : 執行緒讓步
public class TestThread3 {
    public static void main(String[] args) throws Exception {
        
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 1; i < 100;i++){
                    if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName() + "=" + i);
                    }
                }
                    
            }
        },"執行緒1");
        t1.start();
        //執行緒1在 sleep之前就執行完了
        t1.sleep(10000);
        //join方法 迫使t2 必須等執行緒1 執行完 才能執行 然而 t1輸出完自己的 睡著了 t2被迫等了10秒
        t1.join();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 1; i < 100;i++){
                    if(i % 2 != 0){
                        System.out.println(Thread.currentThread().getName() + "=" + i);
                    }
                }
            }
        },"執行緒2");
        t2.start();
        
    }
}

1.3 執行緒的同步

執行緒同步:模擬售票程式,實現三個視窗同時售票 100 張 (1.1案例)

問題:當三個視窗同時訪問共享資料時,產生了無序、重複、超額售票等多執行緒安全問題

解決:將需要訪問的共享資料“包起來”,視為一個整體,確保一次只能有一個執行緒執行流訪問該“共享資料”

Java給上述問題提供了幾種相應的解決方法

(1).同步程式碼塊

synchronized(同步監視器){

//需要訪問的共享資料

}

同步監視器 : 俗稱“鎖”,可以使用任意物件的引用充當,注意確保多個執行緒持有同一把鎖(同一個物件)

(2).同步方法

同步方法 : 在方法宣告處加 synchronized. 注意:非靜態同步方法隱式的鎖 ---- this

例如:

public synchronized void show(){}

(3).同步鎖

同步鎖 : Lock 介面

同步程式碼塊

public class SafeTicket implements Runnable{
    
    private int ticket = 100;
    
    @Override
    public void run() {
        while(true){
            //使用同步程式碼塊
            synchronized (this) {
                if(ticket > 0){
                        System.out.println(Thread.currentThread().getName() + " 完成售票,餘票:" + --ticket);
                }
            }
            
            
        }
    }

}

同步方法:

public class SafeTicket implements Runnable{
    
    private int ticket = 100;
    
    @Override
    public void run() {
        while(true){
            //使用同步程式碼塊
            sale();
            
        }
    }
    
    public synchronized void sale(){
        if(ticket > 0){
            System.out.println(Thread.currentThread().getName() + " 完成售票,餘票:" + 
--ticket);
        }
    }
    
}

同步鎖

public class SafeTicket implements Runnable{
    
    private int ticket = 100;
    
    private Lock l = new ReentrantLock();
    
    @Override
    public void run() {
        while(true){
            l.lock();
            try {
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName() + " 完成售票,餘票:" + 
--ticket);
                }
            } finally {
                l.unlock();//釋放鎖
            }
            
        }
    }
    
}

死鎖

死鎖 是指兩個或兩個以上的程序在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於 死鎖 狀態或系統產生了 死鎖 ,這些永遠在互相等待的程序稱為 死鎖 程序

public class TestDeadLock {
    public static void main(String[] args) {
        final StringBuffer s1 = new StringBuffer();
        final StringBuffer s2 = new StringBuffer();
        
        new Thread() {
            public void run() {
                synchronized (s1) {
                    s2.append("A");
                    
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                    
                    synchronized (s2) {
                        s2.append("B");
                        System.out.print(s1);
                        System.out.print(s2);
                    }
                }
            }
        }.start();
        
        new Thread() {
            public void run() {
                synchronized (s2) {
                    s2.append("C");
                    synchronized (s1) {
                        s1.append("D");
                        System.out.print(s2);
                        System.out.print(s1);
                    }
                }
            }
        }.start();
    }
}

1.4 執行緒的通訊

在 java.lang.Object 類中:

wait() : 使當前“同步監視器”上的執行緒進入等待狀態。同時釋放鎖

notify() / notifyAll() : 喚醒當前“同步監視器”上的(一個/所有)等待狀態的執行緒

注意:上述方法必須使用在同步中

場景1:使用兩個執行緒列印 1-100 執行緒1和執行緒2交替列印

public class MyThread implements Runnable{
    
    int i = 0;
    
    @Override
    public void run() {
        
        while(true){
            synchronized (this) {
                this.notify();
                
                if(i <= 100){
                    System.out.println(Thread.currentThread().getName() + "=" + i++);
                }
                
                try {
                    this.wait();
                } catch (InterruptedException e) {
                }
            }
            
        }
        
    }
    
}
public class TestThread4 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        
        Thread t1 = new Thread(myThread,"執行緒1");
        Thread t2 = new Thread(myThread,"執行緒2");
        
        t1.start();
        t2.start();
    }
}

經典例題:生產者/消費者問題

  • 生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,
  • 店員一次只能持有固定數量的產品(比如:20),如果生產者試圖生產更多的產品,店員會叫生產者停一下,
  • 如果店中有空位放產品了再通知生產者繼續生產;
  • 如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了再通知消費者來取走產品。
public class TestProduct {
    
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        
        Productor pro = new Productor(clerk);
        Customer cus = new Customer(clerk);
        
        new Thread(pro).start();
        new Thread(cus).start();
    }

}

// 店員
class Clerk {

    private int product;

    // 進貨
    public synchronized void getProduct() {
        if (product >= 20) {
            System.out.println("產品已滿!");
            
            try {
                wait();
            } catch (InterruptedException e) {
            }
            
        } else {
            System.out.println("生產者生產了第" + ++product + " 個產品");
            
            notifyAll();
        }
    }

    // 賣貨
    public synchronized void saleProduct() {
        if (product <= 0) {
            System.out.println("缺貨!");
            
            try {
                wait();
            } catch (InterruptedException e) {
            }
            
        } else {
            System.out.println("消費者消費了第" + --product + " 個產品");
            
            notifyAll();
        }
    }

}

// 生產者
class Productor implements Runnable {

    private Clerk clerk;

    public Productor() {
    }

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    public Clerk getClerk() {
        return clerk;
    }

    public void setClerk(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            clerk.getProduct();
        }
    }

}

// 消費者
class Customer implements Runnable {

    private Clerk clerk;

    public Customer() {
    }

    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    public Clerk getClerk() {
        return clerk;
    }

    public void setClerk(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public String toString() {
        return "Customer [clerk=" + clerk + "]";
    }

    @Override
    public void run() {
        while(true){
            clerk.saleProduct();
        }
    }

}