1. 程式人生 > >多執行緒+JAVA學習筆記-DAY24

多執行緒+JAVA學習筆記-DAY24

24.01_多執行緒(多執行緒的引入)(瞭解)

  • 1.什麼是執行緒
    • 執行緒是程式執行的一條路徑, 一個程序中可以包含多條執行緒
    • 多執行緒併發執行可以提高程式的效率, 可以同時完成多項工作
  • 2.多執行緒的應用場景
    • 紅蜘蛛同時共享螢幕給多個電腦
    • 迅雷開啟多條執行緒一起下載
    • QQ同時和多個人一起視訊
    • 伺服器同時處理多個客戶端請求

24.02_多執行緒(多執行緒並行和併發的區別)(瞭解)

  • 並行就是兩個任務同時執行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU)
  • 併發是指兩個任務都請求執行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由於時間間隔較短,使人感覺兩個任務都在執行。
  • 比如我跟兩個網友聊天,左手操作一個電腦跟甲聊,同時右手用另一臺電腦跟乙聊天,這就叫並行。
  • 如果用一臺電腦我先給甲發個訊息,然後立刻再給乙發訊息,然後再跟甲聊,再跟乙聊。這就叫併發。

24.03_多執行緒(Java程式執行原理和JVM的啟動是多執行緒的嗎)(瞭解)

  • A:Java程式執行原理

    • Java命令會啟動java虛擬機器,啟動JVM,等於啟動了一個應用程式,也就是啟動了一個程序。該程序會自動啟動一個 “主執行緒” ,然後主執行緒去呼叫某個類的 main 方法。
  • B:JVM的啟動是多執行緒的嗎

    • JVM啟動至少啟動了垃圾回收執行緒和主執行緒,所以是多執行緒的。

      public class Demo1_Thread {
      
          /**
           * @param args
           * 證明jvm是多執行緒的
           */
          public static void main(String[] args) {
              for(int i = 0; i < 100000; i++) {
                  new Demo();
              }
      
              for(int i = 0; i < 10000; i++) {
                  System.out.println("我是主執行緒的執行程式碼");
              }
          }
      
      }
      
      class Demo {
      
          @Override
          public void finalize() {
              System.out.println("垃圾被清掃了");
          }
      
      }
      

24.04_多執行緒(多執行緒程式實現的方式1)(掌握)

  • 1.繼承Thread

    • 定義類繼承Thread
    • 重寫run方法
    • 把新執行緒要做的事寫在run方法中
    • 建立執行緒物件
    • 開啟新執行緒, 內部會自動執行run方法
    • public class Demo2_Thread {
      
          /**
           * @param args
           */
          public static void main(String[] args) {
              MyThread mt = new MyThread();                           //4,建立自定義類的物件
              mt.start();                                             //5,開啟執行緒
      
              for(int i = 0; i < 3000; i++) {
                  System.out.println("bb");
              }
          }
      
      }
      class MyThread extends Thread {                                 //1,定義類繼承Thread
          public void run() {                                         //2,重寫run方法
              for(int i = 0; i < 3000; i++) {                         //3,將要執行的程式碼,寫在run方法中
                  System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
              }
          }
      }
      

24.05_多執行緒(多執行緒程式實現的方式2)(掌握)

  • 2.實現Runnable

    • 定義類實現Runnable介面
    • 實現run方法
    • 把新執行緒要做的事寫在run方法中
    • 建立自定義的Runnable的子類物件
    • 建立Thread物件, 傳入Runnable
    • 呼叫start()開啟新執行緒, 內部會自動呼叫Runnable的run()方法

      public class Demo3_Runnable {
          /**
           * @param args
           */
          public static void main(String[] args) {
              MyRunnable mr = new MyRunnable();                       //4,建立自定義類物件
              //Runnable target = new MyRunnable();
              Thread t = new Thread(mr);                              //5,將其當作引數傳遞給Thread的建構函式
              t.start();                                              //6,開啟執行緒
      
              for(int i = 0; i < 3000; i++) {
                  System.out.println("bb");
              }
          }
      }
      
      class MyRunnable implements Runnable {                          //1,自定義類實現Runnable介面
          @Override
          public void run() {                                         //2,重寫run方法
              for(int i = 0; i < 3000; i++) {                         //3,將要執行的程式碼,寫在run方法中
                  System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
              }
          }
      
      }
      

24.06_多執行緒(實現Runnable的原理)(瞭解)

  • 檢視原始碼
    • 1,看Thread類的建構函式,傳遞了Runnable介面的引用
    • 2,通過init()方法找到傳遞的target給成員變數的target賦值
    • 3,檢視run方法,發現run方法中有判斷,如果target不為null就會呼叫Runnable介面子類物件的run方法

24.07_多執行緒(兩種方式的區別)(掌握)

  • 檢視原始碼的區別:

    • a.繼承Thread : 由於子類重寫了Thread類的run(), 當呼叫start()時, 直接找子類的run()方法
    • b.實現Runnable : 建構函式中傳入了Runnable的引用, 成員變數記住了它, start()呼叫run()方法時內部判斷成員變數Runnable的引用是否為空, 不為空編譯時看的是Runnable的run(),執行時執行的是子類的run()方法
  • 繼承Thread

    • 好處是:可以直接使用Thread類中的方法,程式碼簡單
    • 弊端是:如果已經有了父類,就不能用這種方法
  • 實現Runnable介面
    • 好處是:即使自己定義的執行緒類有了父類也沒關係,因為有了父類也可以實現介面,而且介面是可以多實現的
    • 弊端是:不能直接使用Thread中的方法需要先獲取到執行緒物件後,才能得到Thread的方法,程式碼複雜

24.08_多執行緒(匿名內部類實現執行緒的兩種方式)(掌握)

  • 繼承Thread類

    new Thread() {                                                  //1,new 類(){}繼承這個類
        public void run() {                                         //2,重寫run方法
            for(int i = 0; i < 3000; i++) {                         //3,將要執行的程式碼,寫在run方法中
                System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
            }
        }
    }.start();
    
  • 實現Runnable介面

    new Thread(new Runnable(){                                      //1,new 介面(){}實現這個介面
        public void run() {                                         //2,重寫run方法
            for(int i = 0; i < 3000; i++) {                         //3,將要執行的程式碼,寫在run方法中
                System.out.println("bb");
            }
        }
    }).start(); 
    

24.09_多執行緒(獲取名字和設定名字)(掌握)

  • 1.獲取名字
    • 通過getName()方法獲取執行緒物件的名字
  • 2.設定名字

    • 通過建構函式可以傳入String型別的名字
    • new Thread("xxx") {
          public void run() {
              for(int i = 0; i < 1000; i++) {
                  System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
              }
          }
      }.start();
      
      new Thread("yyy") {
          public void run() {
              for(int i = 0; i < 1000; i++) {
                  System.out.println(this.getName() + "....bb");
              }
          }
      }.start(); 
      
    • 通過setName(String)方法可以設定執行緒物件的名字
    • Thread t1 = new Thread() {
          public void run() {
              for(int i = 0; i < 1000; i++) {
                  System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
              }
          }
      };
      
      Thread t2 = new Thread() {
          public void run() {
              for(int i = 0; i < 1000; i++) {
                  System.out.println(this.getName() + "....bb");
              }
          }
      };
      t1.setName("芙蓉姐姐");
      t2.setName("鳳姐");
      
      t1.start();
      t2.start();
      

24.10_多執行緒(獲取當前執行緒的物件)(掌握)

  • Thread.currentThread(), 主執行緒也可以獲取

    • new Thread(new Runnable() {
          public void run() {
              for(int i = 0; i < 1000; i++) {
                  System.out.println(Thread.currentThread().getName() + "...aaaaaaaaaaaaaaaaaaaaa");
              }
          }
      }).start();
      
      new Thread(new Runnable() {
          public void run() {
              for(int i = 0; i < 1000; i++) {
                  System.out.println(Thread.currentThread().getName() + "...bb");
              }
          }
      }).start();
      Thread.currentThread().setName("我是主執行緒");                    //獲取主函式執行緒的引用,並改名字
      System.out.println(Thread.currentThread().getName());       //獲取主函式執行緒的引用,並獲取名字
      

24.11_多執行緒(休眠執行緒)(掌握)

  • Thread.sleep(毫秒,納秒), 控制當前執行緒休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000納秒 1000000000

        new Thread() {
            public void run() {
                for(int i = 0; i < 10; i++) {
                    System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    
        new Thread() {
            public void run() {
                for(int i = 0; i < 10; i++) {
                    System.out.println(getName() + "...bb");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    

24.12_多執行緒(守護執行緒)(掌握)

  • setDaemon(), 設定一個執行緒為守護執行緒, 該執行緒不會單獨執行, 當其他非守護執行緒都執行結束後, 自動退出

    • Thread t1 = new Thread() {
          public void run() {
              for(int i = 0; i < 50; i++) {
                  System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                  try {
                      Thread.sleep(10);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      };
      
      Thread t2 = new Thread() {
          public void run() {
              for(int i = 0; i < 5; i++) {
                  System.out.println(getName() + "...bb");
                  try {
                      Thread.sleep(10);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      };
      
      t1.setDaemon(true);                     //將t1設定為守護執行緒
      
      t1.start();
      t2.start();
      

24.13_多執行緒(加入執行緒)(掌握)

  • join(), 當前執行緒暫停, 等待指定的執行緒執行結束後, 當前執行緒再繼續
  • join(int), 可以等待指定的毫秒之後繼續

    • final Thread t1 = new Thread() {
          public void run() {
              for(int i = 0; i < 50; i++) {
                  System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                  try {
                      Thread.sleep(10);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      };
      
      Thread t2 = new Thread() {
          public void run() {
              for(int i = 0; i < 50; i++) {
                  if(i == 2) {
                      try {
                          //t1.join();                        //插隊,加入
                          t1.join(30);                        //加入,有固定的時間,過了固定時間,繼續交替執行
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
      
                          e.printStackTrace();
                      }
                  }
                  System.out.println(getName() + "...bb");
      
              }
          }
      };
      
      t1.start();
      t2.start();
      

24.14_多執行緒(禮讓執行緒)(瞭解)

  • yield讓出cpu

24.15_多執行緒(設定執行緒的優先順序)(瞭解)

  • setPriority()設定執行緒的優先順序(數值越大,執行的次數先對而言多些)

24.16_多執行緒(同步程式碼塊)(掌握)

  • 1.什麼情況下需要同步
    • 當多執行緒併發, 有多段程式碼同時執行時, 我們希望某一段程式碼執行的過程中CPU不要切換到其他執行緒工作. 這時就需要同步.
    • 如果兩段程式碼是同步的, 那麼同一時間只能執行一段, 在一段程式碼沒執行結束之前, 不會執行另外一段程式碼.
  • 2.同步程式碼塊

    • 使用synchronized關鍵字加上一個鎖物件來定義一段程式碼, 這就叫同步程式碼塊
    • 多個同步程式碼塊如果使用相同的鎖物件, 那麼他們就是同步的

      class Printer {
          Demo d = new Demo();
          public static void print1() {
              synchronized(d){                //鎖物件可以是任意物件,但是被鎖的程式碼需要保證是同一把鎖,不能用匿名物件
                  System.out.print("黑");
                  System.out.print("馬");
                  System.out.print("程");
                  System.out.print("序");
                  System.out.print("員");
                  System.out.print("\r\n");
              }
          }
      
          public static void print2() {   
              synchronized(d){    
                  System.out.print("傳");
                  System.out.print("智");
                  System.out.print("播");
                  System.out.print("客");
                  System.out.print("\r\n");
              }
          }
      }
      

24.17_多執行緒(同步方法)(掌握)

  • 使用synchronized關鍵字修飾一個方法, 該方法中所有的程式碼都是同步的

    class Printer {
        public static void print1() {
            synchronized(Printer.class){                //鎖物件可以是任意物件,但是被鎖的程式碼需要保證是同一把鎖,不能用匿名物件
                System.out.print("黑");
                System.out.print("馬");
                System.out.print("程");
                System.out.print("序");
                System.out.print("員");
                System.out.print("\r\n");
            }
        }
        /*
         * 非靜態同步函式的鎖是:this
         * 靜態的同步函式的鎖是:位元組碼物件
         */
        public static synchronized void print2() {  
            System.out.print("傳");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
        }
    }
    

24.18_多執行緒(執行緒安全問題)(掌握)

  • 多執行緒併發操作同一資料時, 就有可能出現執行緒安全問題
  • 使用同步技術可以解決這種問題, 把操作資料的程式碼進行同步, 不要多個執行緒一起操作

        public class Demo2_Synchronized {
    
            /**
             * @param args
             * 需求:鐵路售票,一共100張,通過四個視窗賣完.
             */
            public static void main(String[] args) {
                TicketsSeller t1 = new TicketsSeller();
                TicketsSeller t2 = new TicketsSeller();
                TicketsSeller t3 = new TicketsSeller();
                TicketsSeller t4 = new TicketsSeller();
    
                t1.setName("視窗1");
                t2.setName("視窗2");
                t3.setName("視窗3");
                t4.setName("視窗4");
                t1.start();
                t2.start();
                t3.start();
                t4.start();
            }
    
        }
    
        class TicketsSeller extends Thread {
            private static int tickets = 100;
            static Object obj = new Object();   //如果用引用資料型別成員變數當作鎖物件,必須是靜態的
            public TicketsSeller() {
                super();
    
            }
            public TicketsSeller(String name) {
                super(name);
            }
            public void run() {
                while(true) {
                    synchronized(obj) {
                        if(tickets <= 0) 
                            break;
                        try {
                            Thread.sleep(10);//執行緒1睡,執行緒2睡,執行緒3睡,執行緒4睡
                        } catch (InterruptedException e) {
    
                            e.printStackTrace();
                        }
                        System.out.println(getName() + "...這是第" + tickets-- + "號票");
                    }
                }
            }
        }
    

24.19_多執行緒(火車站賣票的例子用實現Runnable介面)(掌握)

    public class Demo4_Ticket {

        /**
         * @param args
         * 火車站賣票的例子用實現Runnable介面
         */
        public static void main(String[] args) {
            MyTicket mt = new MyTicket();
            new Thread(mt).start();
            new Thread(mt).start();
            new Thread(mt).start();
            new Thread(mt).start();

            /*Thread t1 = new Thread(mt);               //多次啟動一個執行緒是非法的
            t1.start();
            t1.start();
            t1.start();
            t1.start();*/
        }

    }

    class MyTicket implements Runnable {
        private int tickets = 100;
        @Override
        public void run() {
            while(true) {
                synchronized(this) {
                    if(tickets <= 0) {
                        break;
                    }
                    try {
                        Thread.sleep(10);               //執行緒1睡,執行緒2睡,執行緒3睡,執行緒4睡
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "...這是第" + tickets-- + "號票");
                }
            }
        }
    }

24.20_多執行緒(死鎖)(瞭解)

  • 多執行緒同步的時候, 如果同步程式碼巢狀, 使用相同鎖, 就有可能出現死鎖

    • 儘量不要巢狀使用

      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();
      }
      

24.21_多執行緒(以前的執行緒安全的類回顧)(掌握)

  • A:回顧以前說過的執行緒安全問題
    • 看原始碼:Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
    • Vector是執行緒安全的,ArrayList是執行緒不安全的
    • StringBuffer是執行緒安全的,StringBuilder是執行緒不安全的
    • Hashtable是執行緒安全的,HashMap是執行緒不安全的

24.22_多執行緒(總結)