線程與進程


什么是進程

  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.  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM