Java-09 并發(fā)編程

線程的創(chuàng)建

創(chuàng)建并開啟一個新的線程

第一種方法

    // 創(chuàng)建線程
    Thread thread = new Thread(new Runnable(){
      @Override
      public void run() { 
        System.out.println("新線程任務(wù)-----------");
      }
    });
    thread.setName("lwy");
    // 開啟線程
    thread.start();

Thread調(diào)用start方法之后,內(nèi)部會調(diào)用run方法。

注意: 直接調(diào)用run方法并不能開啟新線程,只是在當(dāng)前線程執(zhí)行run里面的任務(wù)而已。 調(diào)用start方法才能開啟新線程(start內(nèi)部有一個native的start0方法,會向內(nèi)核申請開啟新線程)

第二種方法: 創(chuàng)建一個線程類(繼承自Thread)

public class MyThread extends Thread {
  @Override
  public void run() {
    System.out.println("自己的線程類");
  }
}

    Thread thread = new MyThread();
    thread.start();

多線程的內(nèi)存布局

PC寄存器:每個線程都有自己的pc寄存器

java虛擬機(jī)棧:每個線程都有自己的java虛擬機(jī)棧

堆(Heap):多個線程共享

方法區(qū): 多個線程共享方法區(qū)

本地方法棧: 每個線程都有自己的本地方法棧

線程的狀態(tài)

java中線程一共有6種狀態(tài)??梢酝ㄟ^getState方法獲取

    public enum State {
        // 新建(還未啟動)
        NEW,
        // 可運行狀態(tài)(正在JVM中運行?;蛘哒诘却僮飨到y(tǒng)的其他資源(比如處理器))
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
  1. NEW: 新建(還未啟動

  2. RUNNABLE : 可運行狀態(tài)(正在JVM中運行。或者正在等待操作系統(tǒng)的其他資源(比如處理器))

  3. BLOCKED: 阻塞狀態(tài),正在等待監(jiān)視器鎖(內(nèi)部鎖)

  4. WAITING: 等待狀態(tài),在等待另一個線程

  5. TIMED_WAITING:定時等待狀態(tài),

  6. TERMINATED: 終止?fàn)顟B(tài),已經(jīng)執(zhí)行完畢

BLOCK跟WATING的區(qū)別:

一個線程如果正在執(zhí)行任務(wù),會消耗CPU時間片。BLOCK狀態(tài)是等待鎖,
類似于一直執(zhí)行while(鎖沒有被釋放); ,會消耗時間片。而WAITING狀態(tài)就是等待其他線程,處于休眠,CPU不會分配時間片,也不會執(zhí)行其他代碼

線程的方法

sleep、interrupt

可以通過Thread.sleep方法來暫停線程,進(jìn)入WATING狀態(tài)。
在暫停期間,若調(diào)用線程對象的interrupt方法中斷線程,會拋出java.lang.InterruptedExpection異常

    Thread thread = new Thread(() -> {
      System.out.println(1);
      try {
        Thread.sleep(3000);
      } catch (Exception e) {
        e.printStackTrace();
      }
      System.out.println(2);
    });
    
    thread.start();
    
    try {
      Thread.sleep(1000);
    } catch (Exception e) {

    }
    System.out.println(3);

    thread.interrupt();

打印:

1
3
java.lang.InterruptedException: sleep interrupted
        at java.base/java.lang.Thread.sleep(Native Method)
        at Thread.Main.lambda$0(Main.java:12)
        at java.base/java.lang.Thread.run(Thread.java:835)
2

join、isAlive

A.join: 等線程A執(zhí)行完畢之后,當(dāng)前線程再繼續(xù)執(zhí)行任務(wù)。可以傳參制定最長等待時間

    Thread thread = new Thread(()-> {
      System.out.println(1);
      try {
        Thread.sleep(3000);
      } catch (Exception e) {
        e.printStackTrace();
      }
      System.out.println(2);
    });
    thread.start();

    System.out.println(3);

打印

3
1
2

加入等待之后:

    Thread thread = new Thread(()-> {
      System.out.println(1);
      try {
        Thread.sleep(3000);
      } catch (Exception e) {
        e.printStackTrace();
      }
      System.out.println(2);
    });
    thread.start();
    try {
    // 等待線程thread執(zhí)行完畢之后再往下執(zhí)行
      thread.join();
    } catch (Exception e) {
    }
    System.out.println(3);

打印

1
2
3

A.isAlive: 查看線程A是否活著

線程安全問題

多個線程可能會共享(訪問)同一個資源,比如訪問同一個對象,同一個變量,同一個文件。當(dāng)多個線程訪問同一塊資源是,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題,成為線程安全問題

什么時候下會出現(xiàn)線程安全問題?

  • 多個線程共享同一個資源,且至少一個線程正在進(jìn)行寫操作

線程同步

  • 可以使用線程同步技術(shù)來解決線程安全問題
    • 同步語句(Synchronized Statement)
    • 同步方法(Synchronized Method)

線程同步 - 同步語句

  public boolean saleTicket() {
    synchronized (this) {
      if (tickets < 0)
        return false;
      tickets--;
      String name = Thread.currentThread().getName();
      System.out.println(name + "買了一張票, 還剩" + tickets + "張");
      return tickets > 0;
    }
  }
  • synchronized(obj)的原理

    • 每個對象都有一個與他相關(guān)的內(nèi)部鎖(intrinsic lock),或者叫做監(jiān)視器鎖(monitor lock)
    • 第一個執(zhí)行到同步語句的線程會獲得obj的內(nèi)部鎖,在執(zhí)行同步語句結(jié)束后會釋放該鎖
    • 只要一個線程持有了內(nèi)部鎖,那么其他線程在同一時刻無法再獲得該鎖
    • 當(dāng)他們試圖獲取此鎖時,會進(jìn)入BLOCKED狀態(tài)

線程同步 - 同步方法

public synchronized boolean saleTicket() {
    if (tickets < 0)
      return false;
    tickets--;
    String name = Thread.currentThread().getName();
    System.out.println(name + "買了一張票, 還剩" + tickets + "張");
    return tickets > 0;
  }
  • synchronized 不能修飾構(gòu)造方法

  • 修飾實例方法時跟同步語句是等價的

  • 靜態(tài)方法的話等價于synchronized (Class對象)

public synchronized static void test() {

  }
  public static void test1() {
    // Station.class: 類對象 每一個類都只有一個類對象
    synchronized (Station.class) {

    }
  }

死鎖(Deadlock)

什么是死鎖?

兩個或多個線程永遠(yuǎn)阻塞,相互等待對方的鎖


    new Thread(() -> {
      synchronized ("1") {
        System.out.println("1");
        try {
          Thread.sleep(100);
        } catch (Exception e) {
          e.printStackTrace();
        }
        synchronized ("2") {
          System.out.println("1 - 2");
        }
      }
    });

    new Thread(() -> {
      synchronized ("2") {
        System.out.println("2");
        try {
          Thread.sleep(100);
        } catch (Exception e) {
          e.printStackTrace();
        }
        synchronized ("1") {
          System.out.println("2 - 1");
        }
      }
    });

注:同步語句的obj這里傳的是字面量,由于SCP的存在,同一字面量就是同一個對象

線程間通信

可以使用Object.wait,Object.notify,Object.notifyAll方法實現(xiàn)線程間通信

若想在線程A中陳工調(diào)用obj.wait,obj,notify,obj.notifyAll方法,線程A必須要持有obj的內(nèi)部鎖

obj.wait:釋放obj的內(nèi)部鎖,當(dāng)前線程進(jìn)入WATINGTIMED_WAITING狀態(tài)

obj.notifyAll:喚醒所有因為obj.wait進(jìn)入WATINGTIMED_WAITING狀態(tài)的線程

obj.notify:隨機(jī)喚醒一個因為obj.wait進(jìn)入WATINGTIMED_WAITING狀態(tài)的線程

注意:

  1. 調(diào)用waitnotify,notifyAll的obj是同一個obj
  2. 調(diào)用waitnotify,notifyAll的線程必須持有obj的內(nèi)部鎖

可重入鎖(ReentrantLock)

可重入鎖具有跟同步語句,同步方法一樣的一些基本功能,但功能更加強(qiáng)大

什么是可重入?

同一個線程可以重復(fù)獲取同一個鎖。

其他地方叫做遞歸鎖

 private ReentrantLock lock = new ReentrantLock();

  public boolean saleTicket() {
    try {
    // lock():必須獲得此鎖,如果鎖被其他線程獲取 將一直等待直到獲得此鎖
      lock.lock();
      if (tickets < 0)
        return false;
      tickets--;
      String name = Thread.currentThread().getName();
      System.out.println(name + "買了一張票, 還剩" + tickets + "張");
      return tickets > 0;
    } finally {
      lock.unlock();
    }
  }
  • ReentrantLock.lock:獲得此鎖,

    • 如果此鎖沒有被另一個線程持有,則將鎖的持有計數(shù)設(shè)為1.并且此方法立即返回。
    • 如果當(dāng)前線程已經(jīng)持有此鎖,則將此鎖的持有計數(shù)加一,并且此方法立即返回。
    • 如果此鎖被另一個線程持有,那么在獲得此鎖之前,此線程將一直處于休眠狀態(tài),直到獲得此鎖。此時鎖的持有計數(shù)被設(shè)為1(雖然被設(shè)為1,但是此線程并沒有持有該鎖,而是在等待獲取該鎖)。
    • 所以,調(diào)用了幾次lock方法,對應(yīng)的就要有幾次的unlock方法
  • ReentrantLock.tryLock: 僅在鎖沒有被其他線程持有的情況下,才會獲得此鎖

    • 如果此鎖沒有被其他線程持有,則將鎖的持有計數(shù)設(shè)為1,并且立即返回
    • 如果當(dāng)前線程已經(jīng)持有此鎖,則將鎖的持有計數(shù)加一,并且立即返回
    • 如果鎖被另一個線程持有,此方法立即返回false
  public boolean saleTicket() {
    boolean flag = false;
    try {
      flag = lock.tryLock();
      if (tickets < 0)
        return false;
      tickets--;
      String name = Thread.currentThread().getName();
      System.out.println(name + "買了一張票, 還剩" + tickets + "張");
      return tickets > 0;
    } finally {
      if (flag) {
        lock.unlock();
      }
    }
  }
  • ReentrantLock.unlock:嘗試釋放此鎖

    • 如果當(dāng)前線程持有此鎖,則將持有計數(shù)減一
    • 如果持有計數(shù)為0,釋放此鎖
    • 如果當(dāng)前線程沒有持有此鎖,則拋出異常
  • ReentrantLock.isLocked:查看此鎖是否被任意線程持有

其實synchronized也是可重入的

  public static void main(String[] args) {
    synchronized("1") {
      synchronized("1") {
        System.out.println("123456");
      }
    }
  }
  
  // 打?。?23456

假設(shè)synchronized不是可重入鎖,那么在第一個synchronized中,獲得了"1"的內(nèi)部鎖,在第二個synchronized中會發(fā)現(xiàn)“1”的內(nèi)部鎖已經(jīng)被持有,然后會等待,但是“1”本身就是被當(dāng)前的線程持有,所以打印語句永遠(yuǎn)不會被執(zhí)行。
但是現(xiàn)在打印了,說明synchronized是可重入鎖,可以多次獲得該鎖

synchronized在不同語言實現(xiàn)不同,有可能在其他語言就不是可重入鎖(遞歸鎖),再像上面那樣寫的話可能就會出錯

線程池

線程對象占用大量的內(nèi)存,在大型項目中,頻繁的創(chuàng)建和銷毀線程對象將產(chǎn)生大量的內(nèi)存管理開銷。

使用線程池可以最大程度的減少線程創(chuàng)建、銷毀帶來的開銷

線程池由工作線程(Worker Thread)組成

  • 普通線程:執(zhí)行完一個工作之后,生命周期就結(jié)束了

  • 工作線程:可以執(zhí)行多個任務(wù)(沒有工作時就在等待,有任務(wù)了就開始干活)

    • 先將任務(wù)添加到隊列(Queue)中,再從隊列中取出任務(wù)提交到池中
  • 常用的線程池類型是固定線程池(Fixed Thread Pool)
    • 具有固定適量的正在運行的線程
    // 創(chuàng)建線程池
    ExecutorService pool = Executors.newFixedThreadPool(5);
    pool.execute(() -> {
      System.out.println(11 + "_" + Thread.currentThread().getName());
    });
    pool.execute(() -> {
      System.out.println(22 + "_" + Thread.currentThread().getName());
    });
    pool.execute(() -> {
      System.out.println(33 + "_" + Thread.currentThread().getName());
    });
    pool.execute(() -> {
      System.out.println(44 + "_" + Thread.currentThread().getName());
    });

    // 關(guān)閉線程池
    // pool.shutdown();
    
    /*
     * 11_pool-1-thread-1 
     * 22_pool-1-thread-2 
     * 33_pool-1-thread-3 
     * 44_pool-1-thread-4
     */
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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