多線程(1)

一、多線程

  1. 定義
    • 進程
      • 進程是程序執(zhí)行的一條路徑
      • 一個系統(tǒng)級的應用執(zhí)行的就是一個進程
    • 線程
      • 線程在進程的內部汇在,一個進程中可以有多個線程
      • 多個線程并發(fā)執(zhí)行可以提高程序的效率婿奔,可以同時完成多項工作
      • 多線程節(jié)約的是執(zhí)行程序的等待時間,如果程序排列緊密宇攻,沒有等待時間,多線程就不能真正的提高效率了
  2. 多線程的應該
    • 迅雷下載多個任務
    • 服務器同時處理多個請求
  3. 多線程并發(fā)和并行的區(qū)別
    • 并發(fā)是指兩個任務都請求運行倡勇,而處理器只能接受一個任務逞刷,就把這兩個任務安排輪流進行寝受,由于時間間隔較短柬甥,使人感覺兩個任務都在運行
    • 并行就是兩個任務同時運行,就是甲任務運行的同時辽话,乙任務也在進行(需要多核CPU)
    • 如果一臺電腦我先給甲發(fā)個信息扔役,然后立刻給乙發(fā)消息帆喇,然后再跟甲聊,再跟乙聊亿胸,這就叫并發(fā)
    • 比如我跟兩個網(wǎng)友聊天坯钦,左手操作一個電腦跟甲聊,同時右手用另一個電腦跟乙聊天侈玄,這就叫并行
  4. Java虛擬機運行流程
    • java命令會啟動java虛擬機婉刀,啟動JVM,等于啟動了一個應用程序拗馒,也就是啟動了一個進程路星。該進程會自動啟動一個“主線程”,然后主線程去調用某個類中的main方法
    • JVM啟動后至少會創(chuàng)建垃圾回收線程和主線程
  5. 思考
    • 四核CPU和雙核四線程的CPU哪個效率更高诱桂?(四核CPU)

二洋丐、Java中多線程的實現(xiàn)方式一

  1. 步驟

    • 繼承Thread方式
    • 定義類繼承Thread
    • 重寫run方法
    • 把新線程要做的事寫在run方法中
    • 創(chuàng)建線程對象
    • 調用start()方法開啟新線程,內部會自動執(zhí)行run()方法
  2. 演示

    public class MyThread extends Thread{
    
         @Override-
         public void run() {
             while(true){
                 System.out.println("我是子線程");
             }
         }
    }
    
    public static void main(String[] args) {
         
         MyThread myThread = new MyThread();
         myThread.start();
    }
    
  3. 原理解析

    • 創(chuàng)建多線程肯定要跟系統(tǒng)平臺的底層打交道挥等,我們程序猿根本就不知道如何去做友绝,所以,我們僅僅是提供運行代碼肝劲,至于如何創(chuàng)建多線程全靠java實現(xiàn)
    • 繼承Thread的形式迁客,每個Thread的子類對象只能創(chuàng)建一個線程

三郭宝、Java中多線程的實現(xiàn)方式二

  1. 步驟

    • 定義類實現(xiàn)Runnable接口
    • 實現(xiàn)run方法
    • 把新線程要做的事寫在run方法中
    • 創(chuàng)建自定義的Runnable的子類對象
    • 創(chuàng)建Thread對象,傳入Runnable
    • 調用start()開啟新線程掷漱,內部會自動調取Runnable中的run()方法
  2. 演示

    public class MyRunnable implements Runnable{
    
     @Override
     public void run() {
         while(true){
             System.out.println("我是子線程");
         }
     }
    }
    
    public static void main(String[] args) {
     
     MyRunnable myRunnable = new MyRunnable();
    
     Thread thread = new Thread(myRunnable);
         Thread thread2 = new Thread(myRunnable);
     thread.start();
         thread2.start();    
    }
    
  3. 原理

    • Thread類中定義了一個Runnable類型的成員變量target用來接收Runnable的子類對象
    • 當調用Thread對象的start()方法的時候粘室,方法內首先會判斷target是否為null,如果不為null卜范,就調用Runnable子類對象的run方法
    • 多個Thread對象可以共享一個Runnable子類對象

四衔统、多線程兩種方式的區(qū)別

  1. 查看源碼的區(qū)別
    • 繼承Thread:由于子類重寫了Thread類的run(),當調用start()時海雪,直接找子類的run方法
    • 實現(xiàn)Runnable:構造函數(shù)中傳入了Runnable的引用锦爵,成員變量記住了它,start()調用run()方法時內部判斷成員變量Runnable的引用是否為空奥裸,不為空险掀,編譯時看的是Runnable的run(),運行時執(zhí)行的是子類的run方法
  2. 繼承Thread
    • 好處是:可以直接使用Thread類中的方法湾宙,代碼簡單
    • 弊端是:如果已經(jīng)有了父類樟氢,就不能使用這種方法了
  3. 實現(xiàn)Runnable接口
    • 好處是:即使自己定義的線程類有了父類也沒有關系,因為有了父類也可以實現(xiàn)接口创倔,而且接口是可以多實現(xiàn)的嗡害,多個線程可以非常方便的使用同一個對象的成員變量
    • 弊端是:不能直接使用Thread中的方法,需要先獲取到線程對象后畦攘,才能得到Thread的方法霸妹,代碼復雜
  4. 思考
    • 當我們調用start()方法之后,是否就意味著線程立即運行呢知押?(否)
    • CPU的執(zhí)行權(時間片):如果當前線程被CPU執(zhí)行的時候叹螟,我們稱當前線程獲取了CPU的執(zhí)行權
    • 調用start方法之后,意味著當前線程準備好被執(zhí)行了

五台盯、匿名內部類實現(xiàn)多線程的兩種方式

  1. 繼承Thread類

    public static void main(String[] args) {
     
     new Thread(){
         public void run(){
             while(true){
                 System.out.println("我是子線程");
             }
         }
     }.start();
     
    }
    
  2. 實現(xiàn)Runnable接口

    public static void main(String[] args) {
     //創(chuàng)建Thread對象,提供Runnable匿名內部類對象
     new Thread(new Runnable(){
         public void run(){
             while(true){
                 System.out.println("我是子線程");
             }
         }
     }).start();
    }
    

六罢绽、多線程中獲取名字和設置名字

  1. 獲取名字

    • 通過getName()方法獲取線程對象的名字
    • 此方法只適用于Thread的形式
    • Runnable的形式必須通過獲取線程對象來獲取名字
  2. 演示

    public class MyThread extends Thread{
    
     @Override
     public void run() {
         
         System.out.println(this.getName());
     
        }
    }
    
  3. 設置名字

    • 通過構造方法傳入String類型的名字

      public static void main(String[] args) {
        
         new Thread("線程一"){
             public void run(){
                 
                 System.out.println(this.getName());
             }
         }.start();
      }
      
    • 通過setName方法可以設置線程對象的名字

      public static void main(String[] args) {
         
         Thread thread1 = new Thread(){
             public void run(){
                 
                 System.out.println(this.getName());
             }
             
         };
         thread1.setName("線程一");
         thread1.start();
      }
      

七、獲取當前線程的對象名

  1. 定義

    • 我們可以在多線程運行代碼中獲取代表當前執(zhí)行線程的對象静盅,并對其進行操作
  2. 演示

    • 通過獲取對象的形式在Runnable運行代碼中查看當前線程的名稱

      public static void main(String[] args) {
         
         new Thread(new Runnable() {
             
             @Override
             public void run() {
                  //獲取線程的名稱
                 System.out.println(Thread.currentThread().getName());
                 
             }
         }).start();
      
      }
      
    • 通過獲取線程對象的形式設置線程的名稱

      public static void main(String[] args) {
         
         new Thread(new Runnable() {
             @Override
             public void run() {
                 Thread.currentThread().setName("線程二");//設置線程的名稱
                 System.out.println(Thread.currentThread().getName());
             }
         }).start();
      }
      
    • 通過Thread的構造方法設置線程的名稱

      public static void main(String[] args) {
         
         new Thread(new Runnable() {
             
             @Override
             public void run() {
                 
                 System.out.println(Thread.currentThread().getName());
                 
             }
         //通過構造方法設置線程的名稱
         },"線程二").start();
      
      }
      

八良价、休眠線程(sleep)

  1. 定義

    • 在線程的執(zhí)行代碼中調用Thread.sleep()方法,就可以將線程休眠若干毫秒
    • 休眠結束的線程進入可運行狀態(tài)(就緒狀態(tài))而不是直接運行
  2. 演示

    public static void main(String[] args) {
      
     new Thread(new Runnable() {
         @Override
         public void run() {
             
             long time1 = System.currentTimeMillis();
             System.out.println(time1);
             
             try {
                 Thread.sleep(10000);//設置線程等待10000毫秒
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             
             long time2 = System.currentTimeMillis();
             System.out.println(time2);
         }
     //通過構造方法設置線程的名稱
     }).start();
    
    }
    
  3. 測試題(龜兔賽跑)

    • 需求: 烏龜和兔子賽跑總賽程100m, 兔子的速度是10m/s, 烏龜?shù)乃俣仁?m/s.烏龜和兔子都是每跑完10米輸出一次結果, 當兔子跑到70米的時候休息2s ,編程模擬比賽過程

九蒿叠、守護線程(setDaemon)

  1. 定義

    • 圍繞著其他非守護線程運行明垢,該線程不會單獨運行,當其他非守護線程都執(zhí)行結束后市咽,自動退出
    • 調用Thread的setDaemon()方法設置一個線程為守護線程
  2. 應用場景

    • 使用飛秋聊天時痊银,我們打開了多個聊天窗口,當我們關閉主程序時施绎,其他所有的聊天窗口都會隨之關閉
  3. 演示

    public static void main(String[] args) {
     
     Thread t1 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 5; i++) {
                 System.out.println("線程一運行中");
             }
         }
     });
     
     Thread t2 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("守護線程運行..........");
             }
         }
         
     });
     
     t2.setDaemon(true);
     t1.start();
     t2.start();
    
    }
    

十溯革、加入線程(join)

  1. 定義

    • 當前線程暫停贞绳,等待指定的線程執(zhí)行結束后,當前線程再繼續(xù)
  2. 常用方法

    • join()優(yōu)先執(zhí)行指定線程
    • join(毫秒) 優(yōu)先執(zhí)行指定線程若干毫秒
  3. 演示

    public static void main(String[] args) {
     
     Thread t1 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("線程一運行中");
                 try {
                     Thread.sleep(10);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
         
     });
     
     Thread t2 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("線程二運行中..........");
                 try {
                     t1.join(); //讓線程一先執(zhí)行
                     t1.join(100);//讓線程一先執(zhí)行100毫秒
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 try {
                     Thread.sleep(10);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
         
     });
     
     //只有調用了加入線程的線程才會停止運行,其他線程不受影響
     Thread t3 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("線程三運行中..........");
                 try {
                     Thread.sleep(10);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
         
     });
     
     t1.start();
     t2.start();
     t3.start();
    
    }
    
  4. 注意事項

    • 只有調用了加入線程(也就是join方法)的線程才會停止運行致稀,其他線程不受影響

十一冈闭、禮讓線程(了解)(yield)

  1. 定義

    • 僅僅讓出當前線程的一次執(zhí)行權
    • 基本看不到效果
  2. 演示

    public static void main(String[] args) {
     
     Thread t1 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("線程一運行中");
             }
         }
         
     });
     
     Thread t2 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("線程二運行中..........");
                 //讓出當前線程的執(zhí)行權
                 Thread.yield();
                 
             }
         }
     });
    
     t1.start();
     t2.start();
    }
    

十二、設置線程的優(yōu)先級(setPriority)

  1. 定義

    • 每個線程都有優(yōu)先級豺裆,默認都是5拒秘,范圍是1-10,1表示優(yōu)先級最低
    • 優(yōu)先級高的線程在爭奪CPU的執(zhí)行權上有一定的優(yōu)勢臭猜,但是表示絕對的
  2. 演示

    public static void main(String[] args) {
     
     Thread t1 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("線程一運行中");
                 
             }
         }
         
     });
     
     Thread t2 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("線程二運行中..........");
                 
             }
         }
     });
     
     t1.setPriority(1);
     t2.setPriority(10); //線程二的優(yōu)先級高
     t1.start();
     t2.start();
    }
    

十三、多線程安全問題

  1. 定義

    • 多個線程操作同一個數(shù)據(jù)時押蚤,因為線程執(zhí)行的隨機性蔑歌,就有可能出現(xiàn)線程安全問題
    • 使用同步可以解決這個問題,把操作數(shù)據(jù)的代碼進行同步揽碘,同一時間只能有一個線程操作數(shù)據(jù)次屠,這樣就可以保證數(shù)據(jù)的安全性
  2. 線程安全問題演示

    static int num =10;
    public static void main(String[] args) {
     
     Thread t1 = new Thread(){
         public void run(){
             while(true){
                 
                 if (num>0) {
                     System.out.println(getName()+":"+num--);
                 }else{
                     break;
                 }
             }
         }
     };
     Thread t2 = new Thread(){
         public void run(){
             while(true){
                 
                 if (num>0) {
                     System.out.println(getName()+":"+num--);
                 }else{
                     break;
                 }
                 
             }
         }
     };
     t1.start();
     t2.start();
    }
    
  3. 解決安全問題

    • 使用synchronized關鍵字鎖定部分代碼

      static int num = 10;
      public static void main(String[] args) {
         
         Thread t1 = new Thread(){
             public void run(){
                 while(true){
                     //加上鎖
                     synchronized (Class.class) {
                         if (num>0) {
                             System.out.println(getName()+":"+num--);
                         }else{
                             break;
                         }
                     }
      
                 }
             }
         };
         Thread t2 = new Thread(){
             public void run(){
                 while(true){
                     //加上統(tǒng)一把鎖
                     synchronized (Class.class) {
                         if (num>0) {
                             System.out.println(getName()+":"+num--);
                         }else{
                             break;
                         }
                     }
                 }
             }
         };
         t1.start();
         t2.start();
      }
      
    • 注意事項

      • 使用同一把鎖的代碼才能實現(xiàn)同步
      • 沒有獲取到鎖的線程即使得到了CPU的執(zhí)行權,也不能執(zhí)行
      • 盡量減少鎖的范圍雳刺,避免效率低下
      • 鎖可以加在任意類的代碼中或方法上
    • 測試題

      • 火車站總共有100張票, 四個窗口同時賣票, 當票賣光了之后,提示"沒票了...",編程模擬場景

十四劫灶、死鎖

  1. 定義

    • 使用同步的多個線程同時持有對方運行時所需要的資源
    • 多線程同步時,多個同步代碼塊嵌套掖桦,很容易就會出現(xiàn)死鎖
    • 鎖嵌套的越多本昏,越容易造成死鎖的情況
  2. 演示

    • 線程一需要先獲取左邊的筷子然后獲取右邊的筷子才能吃飯
    • 線程二需要先獲取右邊的筷子然后獲取左邊的筷子才能吃飯
    • 當線程一持有左邊的筷子,線程二持有右邊的筷子時,相互等待對方釋放鎖,但又同時持有對方釋放鎖的條件,互不相讓,就會造成無限等待
    private static String s1 = "筷子左";
    private static String s2 = "筷子右";
    
    public static void main(String[] args) {
     new Thread() {
         public void run() {
             while(true) {
                 synchronized(s1) {
                     System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
                     synchronized(s2) {
                         System.out.println(getName() + "...拿到" + s2 + "開吃");
                     }
                 }
             }
         }
     }.start();
     
     new Thread() {
         public void run() {
             while(true) {
                 synchronized(s2) {
                     System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
                     synchronized(s1) {
                         System.out.println(getName() + "...拿到" + s1 + "開吃");
                     }
                 }
             }
         }
     }.start();
    }
    

總結

  1. 多線程
    • 節(jié)省等待時間
    • 平均運行時間
  2. 創(chuàng)建多線程的兩種方式
    • 繼承Thread
    • 實現(xiàn)Runnable
  3. 常用方法
    • Thread.currentThread()
    • Thread.sleep(毫秒)
    • thread.setDeamon()
    • thread.join()
    • Thread.yield()
    • thread.setPriority(1-10)
  4. 多線程的安全問題
    • 多個線程同時爭搶資源,一定會出現(xiàn)問題
    • 同步鎖 synchronized
  5. 死鎖
    • 使用同步的多個線程同時持有對方運行時所需要的資源
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末枪汪,一起剝皮案震驚了整個濱河市涌穆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雀久,老刑警劉巖宿稀,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赖捌,居然都是意外死亡祝沸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門越庇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來罩锐,“玉大人,你說我怎么就攤上這事悦荒∥ㄐ溃” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵搬味,是天一觀的道長境氢。 經(jīng)常有香客問我蟀拷,道長,這世上最難降的妖魔是什么萍聊? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任问芬,我火速辦了婚禮,結果婚禮上寿桨,老公的妹妹穿的比我還像新娘此衅。我一直安慰自己,他們只是感情好亭螟,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布挡鞍。 她就那樣靜靜地躺著,像睡著了一般预烙。 火紅的嫁衣襯著肌膚如雪墨微。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天扁掸,我揣著相機與錄音翘县,去河邊找鬼。 笑死谴分,一個胖子當著我的面吹牛锈麸,可吹牛的內容都是我干的。 我是一名探鬼主播牺蹄,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼忘伞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钞馁?” 一聲冷哼從身側響起虑省,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎僧凰,沒想到半個月后探颈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡训措,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年伪节,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绩鸣。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡怀大,死狀恐怖,靈堂內的尸體忽然破棺而出呀闻,到底是詐尸還是另有隱情化借,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布捡多,位于F島的核電站蓖康,受9級特大地震影響铐炫,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蒜焊,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一倒信、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泳梆,春花似錦鳖悠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鳞溉,卻和暖如春瘾带,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背熟菲。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留朴恳,地道東北人抄罕。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像于颖,于是被迫代替她去往敵國和親呆贿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容