Java多執行緒的實現

滄海一粟發表於2019-01-19

Java多執行緒的實現

用多執行緒只有一個目的:更好的利用cpu資源.燒水的例子.(當洗杯子花5分鐘,執行緒要停5分鐘等待返回結果才能進行後續的燒水操作,新開一個執行緒執行洗杯子操作)。

一、關於執行緒的一些概念

  • cpu時間片:我們作業系統看起來可以多個程式同時執行.分時作業系統,將時間分成長短相同的時間區域,分配給一個執行緒使用,當執行緒還沒有結束,時間片已經過去,該執行緒只有先停止,等待下一個時間片.cpu執行很快,中間的停頓感覺不出來.
  • 多執行緒:指的是這個程式(一個程式)執行時產生了不止一個執行緒(比如,下載程式,開啟多個執行緒同時進行.)
  • 並行:多個cpu例項或者多臺機器同時執行一段處理邏輯,是真正的同時。
  • 併發:通過cpu排程演算法,讓使用者看上去同時執行,實際上從cpu操作層面不是真正的同時。併發往往在場景中有公用的資源,那麼針對這個公用的資源往往產生瓶頸,我們會用TPS或者QPS來反應這個系統的處理能力.
  • 執行緒安全:經常用來描繪一段程式碼。指在併發的情況之下,該程式碼經過多執行緒使用,執行緒的排程順序不影響任何結果。這個時候使用多執行緒,我們只需要關注系統的記憶體,cpu是不是夠用即可。反過來,執行緒不安全就意味著執行緒的排程順序會影響最終結果.
  • Java中的同步指的是通過人為的控制和排程,保證共享資源的多執行緒訪問成為執行緒安全,來保證結果的準確。如上面的程式碼簡單加入@synchronized關鍵字。在保證結果準確的同時,提高效能,才是優秀的程式。執行緒安全的優先順序高於效能.

二、Java多執行緒的實現

1、繼承Thread類建立執行緒

Thread類本質上是實現了Runnable介面,啟動該執行緒的唯一方法是start()方法,

public class MyThread extends Thread{
    //普通的呼叫方法,定義任務要完成的工作.
    @Override
    public void run() {
        System.out.println("新執行緒正在執行,處理相關的邏輯!");
    }
}


public class Test {
    public static void main(String[] args) {
        //例項化物件
        MyThread myThread1 = new MyThread();  
        MyThread myThread2 = new MyThread();    
        //開啟新的執行緒,分配新的資源
        myThread1.start();
        myThread2.start();
    }
}

2、實現Runnable介面建立執行緒

java中是單繼承的,如果繼承了一個類,就不能直接繼承Thread類,需要實現Runnable介面的方式達到開啟新執行緒的目的.

public class MyThread implements Runnable {
    //普通的呼叫方法,定義任務要完成的工作.
    @Override
    public void run() {
        System.out.println("新執行緒正在執行,處理相關的邏輯!");
    }    
 }
 
 
 public class Test {
    public static void main(String[] args) {
         MyThread myThread = new MyThread();  
        Thread thread =new Thread(myThread);
        //開啟新的執行緒,分配新的資源
        thread.start();
    }
}

3、實現Callable介面

Callable介面的call()方法類似run()方法,都是定義任務要完成的工作.主要不同點是call()方法是有返回值的、可以丟擲異常。Callable型別的任務可以有兩種方法開啟執行.

方法一:藉助FutureTask執行(FutureTask、Callable)

將Callable介面物件放到FutureTask物件中,FutureTask的get()方法,可以獲取返回值.

public class MyCallableTask  implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("新執行緒正在執行,處理相關的邏輯!");
        Thread.sleep(3000);
        int sum = 0;
        for(int i=0;i<100;i++) {
            sum += i;
        }
        return sum;
    }    
}

    
public class Test {
    public static void main(String[] args) {
        Callable<Integer> mycallabletask = new MyCallableTask();   
        //由Callable<Integer>建立一個FutureTask<Integer>物件:   
        FutureTask<Integer> futuretask = new FutureTask<Integer>(mycallabletask);   
        //註釋:FutureTask<Integer>是一個包裝器,它通過接受Callable<Integer>來建立,它同時實現了Future            和Runnable介面。 
        //由FutureTask<Integer>建立一個Thread物件:   
        Thread oneThread = new Thread(futuretask);
        oneThread.start();
        try {
            //通過futuretask中get()方法可以得到MyCallableTask的call()執行結果.
            //需要使用時獲取出來,否則出現堵塞,本執行緒要等待新執行緒執行完返回結果才執行
            Integer i = futuretask.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

方法二:藉助執行緒池來執行 (ExecutorService、Callable、Future)

ExecutorService、Callable、Future三個介面實際上都是屬於Executor框架。

執行Callable任務後,可以獲取一個Future的物件,在該物件上呼叫get()就可以獲取到Callable任務返回的Object了。

public class MyCallableTask  implements Callable<Integer> {    
    @Override
    public Integer call() throws Exception {
        System.out.println("新執行緒正在執行,處理相關的邏輯!");
        Thread.sleep(3000);
        int sum = 0;
        for(int i=0;i<100;i++) {
            sum += i;
        }
        return sum;
    }
}

public class Test {
    public static void main(String[] args) {
        int taskSize = 5;
        //建立執行緒池
        ExecutorService threadPool = Executors.newCachedThreadPool(taskSize);
        //提交一個Callable任務,返回一個Future型別
        Future<Integer> future = threadPool.submit(new MyCallableTask());
         try {
            Thread.sleep(3000);//模擬本執行緒的一些任務
            //獲取執行結果get方法是阻塞的
            System.out.println(future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }    
    }
}

採用匿名類直接新建Callable介面

public class Test{
    public static void main(String[] args) {
        // 建立執行緒池
        ExecutorService threadPool = Executors.newCachedThreadPool();
        // 提交一個Callable任務,返回一個Future型別
        Future<Integer> future = threadPool.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("新執行緒正在執行,處理相關的邏輯!");
                Thread.sleep(3000);
                int sum = 0;
                for (int i = 0; i < 100; i++) {
                    sum += i;
                }
                return sum;
            }
        });

        try {
            Thread.sleep(3000);//模擬本執行緒的一些任務
            //獲取執行結果
            System.out.println(future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


三、使用場景

一、Tomcat內部採用了多執行緒,上百個使用者同時訪問同一個web應用,都會新開一個執行緒,呼叫到Servlet程式。如果不使用多執行緒,將序列操作,客戶端將等待別人執行完才能訪問。

二、非同步請求,有兩個任務Task a和Task b,單執行緒只能先進行a再進行b。

三、需要知道執行進度,比如說我們常看到的進度條,任務執行到一定進度給new 一個變數,給變數+1.新開一個執行緒去輪詢這個變數,反饋給客戶端,這樣就可以看到進度情況.

總之,很多地方都用到了多執行緒,多執行緒是為了充分利用cpu資源,當你發現一個業務邏輯執行效率特別低,耗時長,可以考慮使用多執行緒.

相關文章