Java-09 并發(fā)編程

線程的創(chuàng)建

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

第一種方法

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

Thread調用start方法之后茶鹃,內部會調用run方法泊藕。

注意: 直接調用run方法并不能開啟新線程绘趋,只是在當前線程執(zhí)行run里面的任務而已橄务。 調用start方法才能開啟新線程(start內部有一個native的start0方法幔托,會向內核申請開啟新線程)

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

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

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

多線程的內存布局

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

java虛擬機棧:每個線程都有自己的java虛擬機棧

堆(Heap):多個線程共享

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

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

線程的狀態(tài)

java中線程一共有6種狀態(tài)》渑玻可以通過getState方法獲取

    public enum State {
        // 新建(還未啟動)
        NEW,
        // 可運行狀態(tài)(正在JVM中運行重挑∑刃ぃ或者正在等待操作系統(tǒng)的其他資源(比如處理器))
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
  1. NEW: 新建(還未啟動

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

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

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

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

  6. TERMINATED: 終止狀態(tài)隅津,已經執(zhí)行完畢

BLOCK跟WATING的區(qū)別:

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

線程的方法

sleep、interrupt

可以通過Thread.sleep方法來暫停線程协怒,進入WATING狀態(tài)涝焙。
在暫停期間,若調用線程對象的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í)行完畢之后妖滔,當前線程再繼續(xù)執(zhí)行任務隧哮。可以傳參制定最長等待時間

    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是否活著

線程安全問題

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

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

  • 多個線程共享同一個資源搏存,且至少一個線程正在進行寫操作

線程同步

  • 可以使用線程同步技術來解決線程安全問題
    • 同步語句(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)的原理

    • 每個對象都有一個與他相關的內部鎖(intrinsic lock),或者叫做監(jiān)視器鎖(monitor lock)
    • 第一個執(zhí)行到同步語句的線程會獲得obj的內部鎖矢洲,在執(zhí)行同步語句結束后會釋放該鎖
    • 只要一個線程持有了內部鎖璧眠,那么其他線程在同一時刻無法再獲得該鎖
    • 當他們試圖獲取此鎖時,會進入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 不能修飾構造方法

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

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

public synchronized static void test() {

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

    }
  }

死鎖(Deadlock)

什么是死鎖责静?

兩個或多個線程永遠阻塞袁滥,相互等待對方的鎖


    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中陳工調用obj.wait,obj,notify,obj.notifyAll方法,線程A必須要持有obj的內部鎖

obj.wait:釋放obj的內部鎖腰鬼,當前線程進入WATINGTIMED_WAITING狀態(tài)

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

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

注意:

  1. 調用waitnotify,notifyAll的obj是同一個obj
  2. 調用waitnotify,notifyAll的線程必須持有obj的內部鎖

可重入鎖(ReentrantLock)

可重入鎖具有跟同步語句嵌赠,同步方法一樣的一些基本功能,但功能更加強大

什么是可重入熄赡?

同一個線程可以重復獲取同一個鎖姜挺。

其他地方叫做遞歸鎖

 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ù)設為1.并且此方法立即返回。
    • 如果當前線程已經持有此鎖拧篮,則將此鎖的持有計數(shù)加一词渤,并且此方法立即返回。
    • 如果此鎖被另一個線程持有他托,那么在獲得此鎖之前掖肋,此線程將一直處于休眠狀態(tài)仆葡,直到獲得此鎖赏参。此時鎖的持有計數(shù)被設為1(雖然被設為1,但是此線程并沒有持有該鎖沿盅,而是在等待獲取該鎖)把篓。
    • 所以,調用了幾次lock方法腰涧,對應的就要有幾次的unlock方法
  • ReentrantLock.tryLock: 僅在鎖沒有被其他線程持有的情況下韧掩,才會獲得此鎖

    • 如果此鎖沒有被其他線程持有,則將鎖的持有計數(shù)設為1窖铡,并且立即返回
    • 如果當前線程已經持有此鎖疗锐,則將鎖的持有計數(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:嘗試釋放此鎖

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

其實synchronized也是可重入的

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

假設synchronized不是可重入鎖,那么在第一個synchronized中关划,獲得了"1"的內部鎖小染,在第二個synchronized中會發(fā)現(xiàn)“1”的內部鎖已經被持有,然后會等待贮折,但是“1”本身就是被當前的線程持有裤翩,所以打印語句永遠不會被執(zhí)行。
但是現(xiàn)在打印了调榄,說明synchronized是可重入鎖岛都,可以多次獲得該鎖

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

線程池

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

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

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

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

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

    • 先將任務添加到隊列(Queue)中鸽斟,再從隊列中取出任務提交到池中
  • 常用的線程池類型是固定線程池(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());
    });

    // 關閉線程池
    // pool.shutdown();
    
    /*
     * 11_pool-1-thread-1 
     * 22_pool-1-thread-2 
     * 33_pool-1-thread-3 
     * 44_pool-1-thread-4
     */
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市利诺,隨后出現(xiàn)的幾起案子富蓄,更是在濱河造成了極大的恐慌,老刑警劉巖慢逾,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件立倍,死亡現(xiàn)場離奇詭異,居然都是意外死亡侣滩,警方通過查閱死者的電腦和手機口注,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來君珠,“玉大人寝志,你說我怎么就攤上這事〔咛恚” “怎么了材部?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唯竹。 經常有香客問我乐导,道長,這世上最難降的妖魔是什么摩窃? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任兽叮,我火速辦了婚禮芬骄,結果婚禮上,老公的妹妹穿的比我還像新娘鹦聪。我一直安慰自己账阻,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布泽本。 她就那樣靜靜地躺著淘太,像睡著了一般。 火紅的嫁衣襯著肌膚如雪规丽。 梳的紋絲不亂的頭發(fā)上蒲牧,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音赌莺,去河邊找鬼冰抢。 笑死,一個胖子當著我的面吹牛艘狭,可吹牛的內容都是我干的挎扰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼巢音,長吁一口氣:“原來是場噩夢啊……” “哼遵倦!你這毒婦竟也來了?” 一聲冷哼從身側響起官撼,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤梧躺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后傲绣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掠哥,經...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年斜筐,在試婚紗的時候發(fā)現(xiàn)自己被綠了龙致。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡顷链,死狀恐怖,靈堂內的尸體忽然破棺而出屈梁,到底是詐尸還是另有隱情嗤练,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布在讶,位于F島的核電站煞抬,受9級特大地震影響,放射性物質發(fā)生泄漏构哺。R本人自食惡果不足惜革答,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一战坤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧残拐,春花似錦途茫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至错沃,卻和暖如春栅组,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背枢析。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工玉掸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人醒叁。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓排截,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辐益。 傳聞我的和親對象是個殘疾皇子断傲,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354