Java學習筆記 23- 線程之Thread、Runnable、Callable

本文主要內(nèi)容
1、進程和線程
2、Thread類
3、Runnable接口
4、Callable接口
5、線程的狀態(tài)、線程池

01進程和線程

  • A:進程概念

    • a:進程:進程指正在運行的程序。確切的來說,當一個程序進入內(nèi)存運行,
      即變成一個進程,進程是處于運行過程中的程序,并且具有一定獨立功能。
  • B:線程的概念

    • a:線程:線程是進程中的一個執(zhí)行單元(執(zhí)行路徑),負責當前進程中程序的執(zhí)行,
      一個進程中至少有一個線程。一個進程中是可以有多個線程的,這個應用程序也可以稱之為多線程程序。
      簡而言之:一個程序運行后至少有一個進程,一個進程中可以包含多個線程

    • b:多線程:即就是一個程序中有多個線程在同時執(zhí)行。
      一個核心的CPU在多個線程之間進行著隨即切換動作,由于切換時間很短(毫秒甚至是納秒級別),所以感覺不到

    • c:單線程/多線程程序
      單線程程序:即若有多個任務只能依次執(zhí)行。當上一個任務執(zhí)行結束后,下一個任務開始執(zhí)行。如去 網(wǎng)吧上網(wǎng),網(wǎng)吧只能讓一個人上網(wǎng),當這個人下機后,下一個人才能上網(wǎng)。
      多線程程序:即若有多個任務可以同時執(zhí)行。如,去網(wǎng)吧上網(wǎng),網(wǎng)吧能夠讓多個人同時上網(wǎng)。

  • C:線程的運行模式

    • a:分時調(diào)度
      所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間。

    • b:搶占式調(diào)度
      優(yōu)先讓優(yōu)先級高的線程使用 CPU,如果線程的優(yōu)先級相同,那么會隨機選擇一個(線程隨機性),Java使用的為搶占式調(diào)度。

      大部分操作系統(tǒng)都支持多進程并發(fā)運行,現(xiàn)在的操作系統(tǒng)幾乎都支持同時運行多個程序。
      實際上,CPU(中央處理器)使用搶占式調(diào)度模式在多個線程間進行著高速的切換。對于CPU的一個核而言,某個時刻,只能執(zhí)行一個線程,而 CPU的在多個線程間切換速度相對我們的感覺要快,看上去就是在同一時刻運行。
      其實,多線程程序并不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。

  • D:main的主線程

    public class Demo {
    public static void main(String[] args) {
      System.out.println(0/0);
      function();
      System.out.println(Math.abs(-9));
    }
    
    public static void function(){
      for(int i = 0 ; i < 10000;i++){
        System.out.println(i);
      }
    }
    

    }

02Thread類

  • A:Thread類:Thread是程序中的執(zhí)行線程。Java 虛擬機允許應用程序并發(fā)地運行多個執(zhí)行線程。
    創(chuàng)建新執(zhí)行線程有兩種方法

    • a:一種方法是將類聲明為 Thread 的子類。該子類應重寫 Thread 類的 run 方法。創(chuàng)建對象,開啟線程。run方法相當于其他線程的main方法。
    • b:另一種方法是聲明一個實現(xiàn) Runnable 接口的類。該類然后實現(xiàn) run 方法。然后創(chuàng)建Runnable的子類對象,傳入到某個線程的構造方法中,開啟線程。
  • B:實現(xiàn)線程程序繼承Thread

    /*
     *   創(chuàng)建和啟動一個線程
     *   創(chuàng)建Thread子類對象
     *   子類對象調(diào)用方法start()
     *   讓線程程序執(zhí)行,JVM調(diào)用線程中的run
     */
    public class ThreadDemo {
      public static void main(String[] args) {
        SubThread st = new SubThread();
        SubThread st1 = new SubThread();
        st.start();
        st1.start();
        for(int i = 0; i < 50;i++){
          System.out.println("main..."+i);
        }
      }
    }
    /*
     *  定義子類,繼承Thread 
     *  重寫方法run 
     */
    public class SubThread  extends Thread{
      public void run(){
        for(int i = 0; i < 50;i++){
          System.out.println("run..."+i);
        }
      }
    }
    
  • C:什么要繼承Thread

    • a:為什么要繼承Thread類,并調(diào)用其的start方法才能開啟線程呢?
      繼承Thread類:因為Thread類用來描述線程,具備線程應該有功能。那為什么不直接創(chuàng)建Thread類的對象呢?
      如下代碼:

       Thread t1 = new Thread();
       t1.start();//這樣做沒有錯,但是該start調(diào)用的是Thread類中的run方法
              //而這個run方法沒有做什么事情,更重要的是這個run方法中并沒有定義我們需要讓線程執(zhí)行的代碼。
      
    • b:創(chuàng)建線程的目的是什么?
      是為了建立程序單獨的執(zhí)行路徑,讓多部分代碼實現(xiàn)同時執(zhí)行。也就是說線程創(chuàng)建并執(zhí)行需要給定線程要執(zhí)行的任務。
      對于主線程,定義在main函數(shù)中。自定義線程需要執(zhí)行的任務都定義在run方法中。

  • D:多線程內(nèi)存運行過程
    多線程執(zhí)行時,到底在內(nèi)存中是如何運行的呢?
    多線程執(zhí)行時,在棧內(nèi)存中,其實每一個執(zhí)行線程都有一片自己所屬的棧內(nèi)存空間。進行方法的壓棧和彈棧。
    當執(zhí)行線程的任務結束了,線程自動在棧內(nèi)存中釋放了。但是當所有的執(zhí)行線程都結束了,那么進程就結束了。

  • E:Thread類方法

    • a:String getName()獲取線程名字

      public class NameThread extends Thread{
      public NameThread(){
      super("小強");
       }
      
       public void run(){
      System.out.println(getName());
      }
       }
      
      /*
       *  每個線程,都有自己的名字
       *  運行方法main線程,名字就是"main"
       *  其他新鍵的線程也有名字,默認 "Thread-0","Thread-1"
       *  
       *  JVM開啟主線程,運行方法main,主線程也是線程,是線程必然就是
       *  Thread類對象
       */
      public class ThreadDemo {
       public static void main(String[] args) {
         NameThread nt = new NameThread();
         nt.start();
       }
        }
      
    • b:static Thread currentThread()返回正在執(zhí)行的線程對象

          /*
             *  每個線程,都有自己的名字
             *  運行方法main線程,名字就是"main"
             *  其他新鍵的線程也有名字,默認 "Thread-0","Thread-1"
             *  
           *  JVM開啟主線程,運行方法main,主線程也是線程,是線程必然就是
           *  Thread類對象
           *  Thread類中,靜態(tài)方法
           *   static Thread currentThread()返回正在執(zhí)行的線程對象
           */
          public class ThreadDemo {
          public static void main(String[] args) {
            NameThread nt = new NameThread();
              nt.start();
      
            /*Thread t =Thread.currentThread();
          System.out.println(t.getName());*/
          System.out.println(Thread.currentThread().getName());
          }
        }
      

    *c:setName(String name)線程名字設置

    public class NameThread extends Thread{
      
      public NameThread(){
        super("小強");
      }
      
      public void run(){
        System.out.println(getName());
      }
    }
    
    public class ThreadDemo {
      public static void main(String[] args) {
        NameThread nt = new NameThread();
        nt.setName("旺財");
        nt.start();
    
      }
    }
    

    *d:sleep(睡眠毫秒數(shù)) 休眠

       public class ThreadDemo {
    public static void main(String[] args) throws Exception{
      /*for(int i = 0 ; i < 5 ;i++){
        Thread.sleep(50);
        System.out.println(i);
      }*/
      
      new SleepThread().start();
        }
       }
    
       public class SleepThread extends Thread{
    public void run(){
      for(int i = 0 ; i < 5 ;i++){
        try{
          Thread.sleep(500);//睡眠500ms,500ms已到并且cpu切換到該線程繼續(xù)向下執(zhí)行
        }catch(Exception ex){
          
        }
        System.out.println(i);
      }
    }
       }
    

03Runnable接口

  • A:實現(xiàn)線程程序實現(xiàn)Runnable接口

       /*
        *  實現(xiàn)線程成功的另一個方式,接口實現(xiàn)
        *  實現(xiàn)接口Runnable,重寫run方法
        */
       public class SubRunnable implements Runnable{
    public void run(){
      for(int i = 0 ; i < 50; i++){
        System.out.println("run..."+i);
      }
    }
     }
    
  • B:實現(xiàn)接口方式的好處
    第二種方式實現(xiàn)Runnable接口避免了單繼承的局限性,所以較為常用。
    實現(xiàn)Runnable接口的方式,更加的符合面向對象,線程分為兩部分,一部分線程對象,一部分線程任務。
    繼承Thread類,線程對象和線程任務耦合在一起。
    一旦創(chuàng)建Thread類的子類對象,既是線程對象,有又有線程任務。
    實現(xiàn)runnable接口,將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型。Runnable接口對線程對象和線程任務進行解耦。
    (降低緊密性或者依賴性,創(chuàng)建線程和執(zhí)行任務不綁定)

  • C:匿名內(nèi)部類實現(xiàn)線程程序

        /*
         *  使用匿名內(nèi)部類,實現(xiàn)多線程程序
         *  前提: 繼承或者接口實現(xiàn)
         *  new 父類或者接口(){
          *     重寫抽象方法
         *  }
         */
      public class ThreadDemo {
    public static void main(String[] args) {
      //繼承方式  XXX extends Thread{ public void run(){}}
      new Thread(){
        public void run(){
          System.out.println("!!!");
        }
      }.start();
      
      //實現(xiàn)接口方式  XXX implements Runnable{ public void run(){}}
      
      Runnable r = new Runnable(){
        public void run(){
          System.out.println("###");
        }
      };
      new Thread(r).start();
      
      
      new Thread(new Runnable(){
        public void run(){
          System.out.println("@@@");
        }
      }).start();
      
    }
      }
    

04線程的狀態(tài)

  • A:線程的狀態(tài)圖


    image.png
  • B:線程池的原理
    1.在java中,如果每個請求到達就創(chuàng)建一個新線程,開銷是相當大的。
    2.在實際使用中,創(chuàng)建和銷毀線程花費的時間和消耗的系統(tǒng)資源都相當大,甚至可能要比在處理實際的用戶請求的時間和資源要多的多。
    3.除了創(chuàng)建和銷毀線程的開銷之外,活動的線程也需要消耗系統(tǒng)資源。
    如果在一個jvm里創(chuàng)建太多的線程,可能會使系統(tǒng)由于過度消耗內(nèi)存或“切換過度”而導致系統(tǒng)資源不足。
    為了防止資源不足,需要采取一些辦法來限制任何給定時刻處理的請求數(shù)目,盡可能減少創(chuàng)建和銷毀線程的次數(shù),特別是一些資源耗費比較大的線程的創(chuàng)建和銷毀,盡量利用已有對象來進行服務。
    線程池主要用來解決線程生命周期開銷問題和資源不足問題。通過對多個任務重復使用線程,線程創(chuàng)建的開銷就被分攤到了多個任務上了,而且由于在請求到達時線程已經(jīng)存在,所以消除了線程創(chuàng)建所帶來的延遲。這樣,就可以立即為請求服務,使用應用程序響應更快。另外,通過適當?shù)恼{(diào)整線程中的線程數(shù)目可以防止出現(xiàn)資源不足的情況。

05 JDK5實現(xiàn)線程池

  • A:JDK5實現(xiàn)線程池

       /*
         *  JDK1.5新特性,實現(xiàn)線程池程序
         *  使用工廠類 Executors中的靜態(tài)方法創(chuàng)建線程對象,指定線程的個數(shù)
        *   static ExecutorService newFixedThreadPool(int 個數(shù)) 返回線程池對象
        *   返回的是ExecutorService接口的實現(xiàn)類 (線程池對象)
        *   
        *   接口實現(xiàn)類對象,調(diào)用方法submit (Ruunable r) 提交線程執(zhí)行任務
        *          
        */
     public class ThreadPoolDemo {
       public static void main(String[] args) {
     //調(diào)用工廠類的靜態(tài)方法,創(chuàng)建線程池對象
     //返回線程池對象,是返回的接口
     ExecutorService es = Executors.newFixedThreadPool(2);
       //調(diào)用接口實現(xiàn)類對象es中的方法submit提交線程任務
     //將Runnable接口實現(xiàn)類對象,傳遞
     es.submit(new ThreadPoolRunnable());
     es.submit(new ThreadPoolRunnable());
     es.submit(new ThreadPoolRunnable());
    
       }
     }
    
     public class ThreadPoolRunnable implements Runnable {
     public void run(){
     System.out.println(Thread.currentThread().getName()+" 線程提交任務");
     }
     }
    

06 Callable接口

  • A:實現(xiàn)線程的Callable接口方式

    /*
      *  實現(xiàn)線程程序的第三個方式,實現(xiàn)Callable接口方式
      *  實現(xiàn)步驟
       *    工廠類 Executors靜態(tài)方法newFixedThreadPool方法,創(chuàng)建線程池對象
       *    線程池對象ExecutorService接口實現(xiàn)類,調(diào)用方法submit提交線程任務
     *    submit(Callable c)
     */
    public class ThreadPoolDemo1 {
     public static void main(String[] args)throws Exception {
     ExecutorService es = Executors.newFixedThreadPool(2);
     //提交線程任務的方法submit方法返回 Future接口的實現(xiàn)類
     Future<String> f = es.submit(new ThreadPoolCallable());
     String s = f.get();
     System.out.println(s);
     }
    }
      /*
     * Callable 接口的實現(xiàn)類,作為線程提交任務出現(xiàn)
     * 使用方法返回值
     */
    
    import java.util.concurrent.Callable;
    
    public class ThreadPoolCallable implements Callable<String>{
     public String call(){
       return "abc";
     }
    }
    
  • B:線程實現(xiàn)異步計算

    /*
     * 使用多線程技術,求和
     * 兩個線程,1個線程計算1+100,另一個線程計算1+200的和
     * 多線程的異步計算
     */
    public class ThreadPoolDemo {
    public static void main(String[] args)throws Exception {
      ExecutorService es = Executors.newFixedThreadPool(2);
      Future<Integer> f1 =es.submit(new GetSumCallable(100));
      Future<Integer> f2 =es.submit(new GetSumCallable(200));
      System.out.println(f1.get());
      System.out.println(f2.get());
      es.shutdown();
    }
    }
    
    
    public class GetSumCallable implements Callable<Integer>{
    private int a;
    public GetSumCallable(int a){
      this.a=a;
    }
    
    public Integer call(){
      int sum = 0 ;
      for(int i = 1 ; i <=a ; i++){
        sum = sum + i ;
      }
      return sum;
    }
    }
    
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容