執行緒與程式

誰有特馬肉發表於2021-09-11

什麼是程式

  1. 程式是指執行中的程式

  2. 程式是程式的一次執行過程,或是正在執行的一個程式。是動態過程:有它自身的產生、存在和消亡的過程

解釋:電腦開啟一個程式,程式一執行就是程式,程式會佔用記憶體空間,關閉程式。記憶體釋放

什麼是執行緒

  1. 執行緒時有程式建立的,是程式的一個實體

  2. 一個程式可以擁有多個執行緒

解釋:開啟迅雷是一個程式,迅雷同時下載多個資源,為多個執行緒

什麼是單執行緒和多執行緒

  1. 單執行緒:同一時刻,只允許執行一個執行緒

  2. 多執行緒:同一時刻,可以同時執行多個執行緒

解釋:迅雷同時下載多個資源,為多個執行緒---多執行緒

什麼是併發和並行

  1. 併發:同一時刻,多個任務交替執行。也就是單核CPU實現的多工就是併發

  2. 並行:同一時刻,多個任務同時執行,多核CPU可以實現並行

解釋

  1. 併發:一個CPU來回執行多個程式

  2. 並行:兩個CPU同時各自實現自己的一個程式

  3. 併發和並行同時存在:兩個CPU同時各自實現自己的多個程式

擴充套件:

獲取自己電腦上有幾個CPU

Runtime runtime = Runtime.getRuntime();
int cpunums = runtime.availableProcessors();

建立執行緒

  1. 繼承Thread類,重寫run方法

  2. 實現Runnable介面,重寫run()方法

入門案例1---繼承Thread類

要求:1. 編寫程式,開啟一個執行緒,該執行緒每個1秒。在控制檯輸出“喵喵,我是小貓咪”,當輸出80次後,結束該程式

package threaduse;

//通過繼承Thread類,建立執行緒
public class Thread01 {
   public static void main(String[] args) {
       cat cat = new cat();
       cat.start();
  }
}

class cat extends Thread {
   @Override
   public void run() {
       int c = 0;
       while (true) {
           System.out.println("喵喵,我是一隻小貓咪"+"第"+(++c)+"次"+Thread.currentThread().getName());
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           if(c==80){
               break;
          }
      }
  }
}

多執行緒機制

  1. 執行程式碼:開啟程式

  2. 執行main方法:開啟main執行緒

  3. main方法執行執行緒類的start方法:開啟執行緒類的執行緒

  4. main執行緒啟動了執行緒類,不會發生阻塞,會和執行緒類的執行緒交替執行

需知點

  1. main執行緒會和執行緒類執行緒同時執行

  2. main執行緒可以繼續開其他執行緒,執行緒類也可以開其他執行緒

  3. main執行緒結束、執行緒類執行緒不會結束

  4. 程式中的全部執行緒結束,程式才會消失

為什麼呼叫是start方法,而不是run方法

  1. 呼叫start方法是啟動執行緒---》最終會呼叫執行緒類的run方法

  2. 直接呼叫run方法的話,run方法其實就是一個普通的方法,沒有真正的啟動執行緒,會把run方法執行完,才會先下執行

  3. 真正實現多執行緒的效果是start0方法,而不run方法

步驟是:

  1. start方法

  2. start0方法 start0()是本地方法,由JVM呼叫

  3. 實際上是:在start0方法裡面用多執行緒的機制來呼叫run方法

入門案例---實現Runnable介面

說明:java是單繼承的,在某些情況下,一個繼承了某個父類,就不能再繼承Thread類,這就用到了介面

需求:請編寫程式,該程式可以每隔1秒,在控制檯輸出hi,輸出10次後,自動退出,用Runnable介面方式實現

package threaduse;

public class Runnable01 {
   public static void main(String[] args) {
       Dog dog = new Dog();
       Thread thread = new Thread(dog);
       thread.start();
  }
}
class Dog implements Runnable{
   @Override
   public void run() {
       int times = 0;
       while (true) {
           System.out.println("hi" + (++times));
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           if(times==10){
               break;
          }
      }
  }
}

需知點:

  1. 因為Dog類沒辦法呼叫start方法

  2. 所以建立了Thread類,把dog物件放入Thread物件中,就可以呼叫run方法了,因為這裡底層使用設計模式【代理模式】

程式碼模擬 瞭解代理模式

解釋為啥可以把dog物件放入Thread物件中,就可以呼叫run方法了

package threaduse;

public class Runnable01 {
   public static void main(String[] args)
       Tiger tiger = new Tiger();
       ThreadProxy threadProxy = new ThreadProxy(tiger);
       threadProxy.start();//1.
  }
}
class Animal{}
class Tiger extends Animal implements Runnable{
   @Override
   public void run() {//6
       System.out.println("嗷嗷叫");
  }
}
//模擬了一個極簡的Thread類
class ThreadProxy implements  Runnable{
   private Runnable target = null;
   @Override
   public void run() {//5.
       if(target != null){
           target.run();//target 是Runnable型別的
      }
  }

   public ThreadProxy(Runnable target) {//2
       this.target = target;
  }
   public void  start(){//3
       start0();
  }
   public void start0(){//4
       run();
  }
}

入門案例---多執行緒執行

需求:編寫一個程式,建立兩個執行緒,一個執行緒每隔1秒輸出“HelloWorld”,輸出10次,退出。一個執行緒每隔1秒輸出“Hi",輸出5次退出

package threaduse;

public class Thread02 {
   public static void main(String[] args) {
       T t = new T();
       TT tt = new TT();
       Thread thread1 = new Thread(t);
       Thread thread2 = new Thread(tt);
       thread1.start();
       thread2.start();
  }

}
class T implements  Runnable{
   @Override
   public void run() {
       int a = 0;
       while (true){
           System.out.println("Hello,World"+(++a));
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           if(a==10){
               break;
          }
      }
  }
}
class TT implements Runnable{
   @Override
   public void run() {
       int a = 0;
       while (true) {
           System.out.println("Hi" + (++a));
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           if(a==5){
               break;
          }
      }
  }
}

執行緒終止

  1. 當執行緒完成任務後,會自動退出

  2. 通過使用變數來控制run方法退出的方式停止執行緒,即通知方式

package threaduse;


public class ThreadExit {
   public static void main(String[] args) throws InterruptedException {
       T1 t1 = new T1();
       Thread thread = new Thread(t1);
       thread.start();

       Thread.sleep(10*1000);
       //如果希望main執行緒去控制t1 執行緒的終止,必須修改a的值
       t1.setA(false);

  }
}
class T1 implements  Runnable{
   int b = 0;
   private boolean a = true;
   @Override
   public void run() {
       while (a){
           System.out.println("你好"+(++b));
           try {
               Thread.sleep(50);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  }
   public void setA(boolean a) {
       this.a = a;
  }
}

執行緒常用方法

  • 常用方法1

    1. setName 設定執行緒名稱,使之與引數的name相同

    2. getName 返回執行緒的名稱

    3. start 使執行緒開始執行;java虛擬機器底層呼叫該執行緒的start0方法

    4. run 呼叫執行緒物件run方法

    5. setPriority 更改執行緒的優先順序

    6. getPriority 獲取執行緒的優先順序

    7. sleep 在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行)

    8. interrupt 中斷執行緒

  • 常用方法1注意點

    1. start底層會建立新的執行緒,呼叫run,run就是一個簡單的方法呼叫,不會啟動新執行緒

    2. 執行緒的優先順序範圍 MAX_PRIORITY 10 MIN_PRIORITY 1 NORM_PRIORITY 5

    3. interrupt,中斷執行緒,但並沒有真正的結束執行緒。一般用於中斷正在休眠的執行緒

    4. sleep:執行緒的靜態方法,使當前執行緒休眠

  • 常用方法1程式碼實現

     package threaduse;

    public class ThreadMethod01 {
       public static void main(String[] args) throws InterruptedException {
           T2 t2 = new T2();
           t2.setName("常世超");
           t2.setPriority(Thread.MIN_PRIORITY);
           t2.start();//啟動子執行緒

           //讓主執行緒列印5次,中斷子執行緒的休眠
           for (int i = 0; i <5 ; i++) {
               Thread.sleep(1000);
               System.out.println("hi"+i);
          }
           t2.getPriority();
           t2.interrupt();//當執行到這裡 就會中斷t2執行緒的休眠
      }
    }
    class T2 extends  Thread{
       @Override
       public void run() {
           while (true) {
               for (int i = 1; i <= 100; i++) {
                   System.out.println(Thread.currentThread().getName() + "吃" + i);
              }
               try {
                   Thread.sleep(1000*20);
              } catch (InterruptedException e) {
                   //當執行緒執行到了一個interrupt方法,就會catch 一個異常
                   System.out.println("中斷執行緒");
              }
          }
      }
    }
  • 常用方法2

    1. yield :執行緒的禮讓,讓其他執行緒執行,但禮讓的時間不確定,所以不一定禮讓成功

    2. join :執行緒插隊,插隊的執行緒一旦插隊成功,則肯定先執行完插入的執行緒所有任務

  • 常用方法2程式碼實現

    package threaduse;

    public class ThreadMethod02 {
       public static void main(String[] args) throws InterruptedException {
           T3 t3 = new T3();
           Thread thread = new Thread(t3);
           thread.start();
           for (int i = 1; i <= 20; i++) {
               Thread.sleep(1000);
               System.out.println("main正在進行第" + i + "次衝刺");
               if (i == 10) {
                   thread.yield();//執行緒的禮讓
    //               thread.join();//執行緒插隊
              }
          }

      }
    }

    class T3 implements Runnable {
       @Override
       public void run() {
           for (int i = 1; i <= 20; i++) {
               System.out.println("T3正在進行第" + i + "次衝刺");
               try {
                   Thread.sleep(1000);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }

      }
    }
  • 常用方法3

    1. 使用者執行緒:也叫工作執行緒,當執行緒的任務執行完或者通知方式結束

    2. 守護執行緒:一般是為工作執行緒服務的,當所有的使用者執行緒結束,守護執行緒自動結束

    3. 常見的守護執行緒:垃圾回收機制

  • 程式碼實現

    package threaduse;

    public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
    A2 a2 = new A2();
    a2.setDaemon(true);
    a2.start();
    for (int i = 1; i <=10 ; i++) {
    System.out.println("我是使用者執行緒"+i);
    Thread.sleep(1000);
    }

    }
    }
    class A2 extends Thread{
    @Override
    public void run() {
    for ( ; ;) {
    System.out.println("我是守護執行緒");
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }

     

常用方法練習題

package threaduse;
/*1. 主執行緒每個1秒,輸出hi,一共10次
* 2.當輸出到 hi 5時 ,啟動一個子執行緒(要求實現Runnable介面),每個1s輸出hello,等該執行緒輸出10次後,退出
* 3.主執行緒繼續輸出hi,直到執行緒結束*/
public class ThreadMethodTest {
public static void main(String[] args) throws InterruptedException {
A a = new A();
Thread thread = new Thread(a);
for (int i = 1;i<=10;i++){
Thread.sleep(1000);
System.out.println("hi"+i);

if(i==5){
thread.start();
thread.join();
}
}
}
}
class A implements Runnable{
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
System.out.println("hello"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

執行緒的生命週期

  • NEW 至今尚未啟動的執行緒處於這種狀態。 ---建立狀態

  • RUNNABLE 正在 Java 虛擬機器中執行的執行緒處於這種狀態。 ----可執行狀態

    • Ready----就緒狀態

    • Running----執行狀態

  • BLOCKED 受阻塞並等待某個監視器鎖的執行緒處於這種狀態。 ----阻塞狀態

  • WAITING 無限期地等待另一個執行緒來執行某一特定操作的執行緒處於這種狀態。 ---等待狀態

  • TIMED_WAITING 等待另一個執行緒來執行取決於指定等待時間的操作的執行緒處於這種狀態。 ---超時等待狀態

  • TERMINATED 已退出的執行緒處於這種狀態。

售票案例引出執行緒同步鎖

出現的問題:

  • 問題1.重賣:一張票賣給了多個人

  • 問題2.超賣:出現了票數為0甚至是負數的情況

package threaduse;

public class Mai {
public static void main(String[] args) {
Csc csc = new Csc();
Thread thread1 = new Thread(csc);
Thread thread2 = new Thread(csc);
Thread thread3 = new Thread(csc);
thread1.start();
thread2.start();
thread3.start();
}
}
class Csc implements Runnable{
int a = 100;
@Override
public void run() {
while (true){
if(a<=0) {
System.out.println("售票結束");
break;
}
try {
//讓程式休眠後出現的兩個問題:
//問題1.重賣:一張票賣給了多個人
//問題2.超賣:出現了票數為0甚至是負數的情況
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("視窗"+Thread.currentThread().getName()+"\t"+(--a));
}
}
}

同步鎖

  • 判斷程式有沒有出現執行緒安全問題:

    在多執行緒程式中 + 有共享資料 + 多條語句操作共享資料

  • 同步與非同步:

    • 同步:體現了排隊的效果,同一時刻只能有一個執行緒佔用資源,其他沒有許可權的執行緒排隊

      壞處:效率低,

      好處:安全

    • 非同步:體現了多執行緒搶佔資源的效果,執行緒間互想不等待,互相搶佔資源

      壞處:有安全隱患

      好處:效率高

  • 同步鎖關鍵字:synchronized

    • 實現同步具體方法
      1. 同步程式碼塊

        synchronized (鎖物件){//使用的鎖物件型別任意,但注意:必須唯一!
        //需要同步的程式碼(也就是可能出現問題的操作共享資料的多條語句);
        }
      2. 同步方法

        public synchronized void m(){
        //需要同步的程式碼
        }
    • 使用同步鎖的前提
      1. 同步需要兩個或者兩個以上的執行緒(單執行緒無需考慮多執行緒安全問題)

      2. 多個執行緒間必須使用同一個鎖(我上鎖後其他人也能看到這個鎖,不然我的鎖鎖不住其他人,就沒有了上鎖的效果)

    • 鎖物件

      • (非靜態的)同步方法的鎖可以是this,也可以是其他物件(要求是同一物件)

            public synchronized void aVoid(){
        System.out.println("我愛你");
        }
        //解釋(非靜態的)同步方法的鎖可以是this,也可以是其他物件(要求是同一物件)
        public void a(){
        Object o = new Object();
        synchronized (this){
        // synchronized (o){
        System.out.println("我愛你");
        }
        }

         

      • (靜態的)同步方法的鎖物件是當前類本身

         public static synchronized void a(){
        System.out.println("我愛你");
        }
        //解釋靜態的同步方法鎖物件時類本身
        public static void a(){
        synchronized (Csc.class){
        System.out.println("我愛你");
        }
        }

         

      • 同步程式碼塊的鎖:

        @Override  
        public void run() {
        Object o = new Object();
        synchronized (o) {}
        }
        @Override
        public void run() {
        synchronized (this) {}
        }

執行緒的死鎖

  • 介紹

    多個執行緒都佔用了對方的鎖資源、但不肯想讓,導致了死鎖,在程式設計是一定要避免死鎖的發生

  • 應用案例

    媽媽:你先寫作業,我讓你玩手機

    兒子:你先讓我玩手機,我就寫作業

  • 程式碼實現

    package threaduse;

    public class DeadLock {
    public static void main(String[] args) {
    DeadLockDemo A = new DeadLockDemo(true);
    DeadLockDemo B = new DeadLockDemo(false);
    A.setName("執行緒A");
    B.setName("執行緒B");
    A.start();
    B.start();
    }
    }
    class DeadLockDemo extends Thread{
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag ;
    public DeadLockDemo(boolean flag) {
    this.flag = flag;
    }

    @Override
    public void run() {
    /*下面有業務邏輯
    * 1.如果flag 為T ,執行緒A 就會先得到/持有 o1 物件鎖,然後嘗試去獲取o2 物件鎖
    * 2.如果執行緒A 得不到 o2 物件鎖,就會Blocked
    * 3.如果flag 為F , 執行緒B就會就會先得到/持有 o2 物件鎖,然後嘗試去獲取o1 物件鎖
    * 4.如果執行緒B 得不到 o1 物件鎖,就會Blocked*/
    if(flag) {
    synchronized (o1) {
    System.out.println(Thread.currentThread().getName()+"進入狀態1");
    synchronized (o2) {
    System.out.println(Thread.currentThread().getName()+"進入狀態2");
    }
    }
    }else {
    synchronized (o2) {
    System.out.println(Thread.currentThread().getName()+"進入狀態3");
    synchronized (o1) {
    System.out.println(Thread.currentThread().getName()+"進入狀態4");
    }
    }
    }
    }
    }

釋放鎖

  • 下面操作會釋放鎖

    1. 當前執行緒的同步方法、同步程式碼塊執行結束

      案例:上廁所,完事出來

    2. 當前執行緒在同步方法、同步程式碼塊中遇到break、return。

      案例:沒有正常完事,經理叫他修BUG,不得不出來

    3. 當前執行緒在同步方法、同步程式碼塊中出現了未處理的Error或Exception,導致異常結束

      案例:沒有正常完事,發現為帶紙,不得不出來

    4. 當前執行緒在同步方法、同步程式碼塊中執行了wait()方法,當前執行緒暫停,並釋放鎖

      案例:沒有正常完事,覺得需要醞釀一下,所以出來等會再進去

  • 下面操作不會釋放鎖

    1. 執行緒執行同步方法、程式碼塊時,程式呼叫了Thread.sleep()、Thread.yield()方法暫停當前執行緒的執行、不會釋放鎖

      案例:上廁所,眯一會

    2. 執行緒執行同步方法、程式碼塊時,其他執行緒呼叫了suspend()方法將執行緒掛起,該執行緒不會釋放鎖

      提示:應儘量避免使用suspend()和resume()來控制執行緒,方法不再推薦使用

    3.  

相關文章