一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - java高并發之線程的基本操作詳解

java高并發之線程的基本操作詳解

2022-03-02 00:46路人甲Java Java教程

本文主要介紹java高并發線程的基本操作,這里整理詳細的資料來解釋線程的知識,有需要的學習高并發的朋友可以參考下

新建線程

新建線程很簡單。只需要使用new關鍵字創建一個線程對象,然后調用它的start()啟動線程即可。

Thread thread1 = new Thread1();	
t1.start();

那么線程start()之后,會干什么呢?線程有個run()方法,start()會創建一個新的線程并讓這個線程執行run()方法。

這里需要注意,下面代碼也能通過編譯,也能正常執行。但是,卻不能新建一個線程,而是在當前線程中調用run()方法,將run方法只是作為一個普通的方法調用。

Thread thread1 = new Thread1();	
thread1.run();

所以,希望大家注意,調用start方法和直接調用run方法的區別。

start方法是啟動一個線程,run方法只會在當前線程中串行的執行run方法中的代碼。

默認情況下, 線程的run方法什么都沒有,啟動一個線程之后馬上就結束了,所以如果你需要線程做點什么,需要把您的代碼寫到run方法中,所以必須重寫run方法。

Thread thread1 = new Thread() {	
          @Override	
          public void run() {	
              System.out.println("hello,我是一個線程!");	
          }	
      };	
thread1.start();

上面是使用匿名內部類實現的,重寫了Thread的run方法,并且打印了一條信息。我們可以通過繼承Thread類,然后重寫run方法,來自定義一個線程。但考慮java是單繼承的,從擴展性上來說,我們實現一個接口來自定義一個線程更好一些,java中剛好提供了Runnable接口來自定義一個線程。

@FunctionalInterface	
public interface Runnable {	
  public abstract void run();	
}

Thread類有一個非常重要的構造方法:

public Thread(Runnable target) 

我們在看一下Thread的run方法:

public void run() {	
      if (target != null) {	
          target.run();	
      }	
  }

當我們啟動線程的start方法之后,線程會執行run方法,run方法中會調用Thread構造方法傳入的target的run方法。

實現Runnable接口是比較常見的做法,也是推薦的做法。

 

終止線程

一般來說線程執行完畢就會結束,無需手動關閉。但是如果我們想關閉一個正在運行的線程,有什么方法呢?可以看一下Thread類中提供了一個stop()方法,調用這個方法,就可以立即將一個線程終止,非常方便。

package com.itsoku.chat01;	
import lombok.extern.slf4j.Slf4j;	
import java.util.concurrent.TimeUnit;	
/**	
* <b>description</b>:<br>	
* <b>time</b>:2019/7/12 17:18 <br>	
* <b>author</b>:微信公眾號:路人甲Java,專注于java技術分享(帶你玩轉 爬蟲、分布式事務、異步消息服務、任務調度、分庫分表、大數據等),喜歡請關注!	
*/	
@Slf4j	
public class Demo01 {	
  public static void main(String[] args) throws InterruptedException {	
      Thread thread1 = new Thread() {	
          @Override	
          public void run() {	
              log.info("start");	
              boolean flag = true;	
              while (flag) {	
                  ;	
              }	
              log.info("end");	
          }	
      };	
      thread1.setName("thread1");	
      thread1.start();	
      //當前線程休眠1秒	
      TimeUnit.SECONDS.sleep(1);	
      //關閉線程thread1	
      thread1.stop();	
      //輸出線程thread1的狀態	
      log.info("{}", thread1.getState());	
      //當前線程休眠1秒	
      TimeUnit.SECONDS.sleep(1);	
      //輸出線程thread1的狀態	
      log.info("{}", thread1.getState());	
  }	
}

運行代碼,輸出:

18:02:15.312 [thread1] INFO com.itsoku.chat01.Demo01 - start
18:02:16.311 [main] INFO com.itsoku.chat01.Demo01 - RUNNABLE
18:02:17.313 [main] INFO com.itsoku.chat01.Demo01 - TERMINATED

代碼中有個死循環,調用stop方法之后,線程thread1的狀態變為TERMINATED(結束狀態),線程停止了。

我們使用idea或者eclipse的時候,會發現這個方法是一個廢棄的方法,也就是說,在將來,jdk可能就會移除該方法。

stop方法為何會被廢棄而不推薦使用?stop方法過于暴力,強制把正在執行的方法停止了。

大家是否遇到過這樣的場景:電力系統需要維修,此時咱們正在寫代碼,維修人員直接將電源關閉了,代碼還沒保存的,是不是很崩潰,這種方式就像直接調用線程的stop方法類似。線程正在運行過程中,被強制結束了,可能會導致一些意想不到的后果。可以給大家發送一個通知,告訴大家保存一下手頭的工作,將電腦關閉。

 

線程中斷

在java中,線程中斷是一種重要的線程寫作機制,從表面上理解,中斷就是讓目標線程停止執行的意思,實際上并非完全如此。在上面中,我們已經詳細討論了stop方法停止線程的壞處,jdk中提供了更好的中斷線程的方法。嚴格的說,線程中斷并不會使線程立即退出,而是給線程發送一個通知,告知目標線程,有人希望你退出了!至于目標線程接收到通知之后如何處理,則完全由目標線程自己決定,這點很重要,如果中斷后,線程立即無條件退出,我們又會到stop方法的老問題。

Thread提供了3個與線程中斷有關的方法,這3個方法容易混淆,大家注意下:

public void interrupt() //中斷線程	
public boolean isInterrupted() //判斷線程是否被中斷	
public static boolean interrupted()  //判斷線程是否被中斷,并清除當前中斷狀態

interrupt()方法是一個實例方法,它通知目標線程中斷,也就是設置中斷標志位為true,中斷標志位表示當前線程已經被中斷了。isInterrupted()方法也是一個實例方法,它判斷當前線程是否被中斷(通過檢查中斷標志位)。最后一個方法interrupted()是一個靜態方法,返回boolean類型,也是用來判斷當前線程是否被中斷,但是同時會清除當前線程的中斷標志位的狀態。

while (true) {	
          if (this.isInterrupted()) {	
              System.out.println("我要退出了!");	
              break;	
          }	
      }	
  }	
};	
thread1.setName("thread1");	
thread1.start();	
TimeUnit.SECONDS.sleep(1);	
thread1.interrupt();

上面代碼中有個死循環,interrupt()方法被調用之后,線程的中斷標志將被置為true,循環體中通過檢查線程的中斷標志是否為ture( this.isInterrupted())來判斷線程是否需要退出了。

再看一種中斷的方法:

static volatile boolean isStop = false;	
public static void main(String[] args) throws InterruptedException {	
  Thread thread1 = new Thread() {	
      @Override	
      public void run() {	
          while (true) {	
              if (isStop) {	
                  System.out.println("我要退出了!");	
                  break;	
              }	
          }	
      }	
  };	
  thread1.setName("thread1");	
  thread1.start();	
  TimeUnit.SECONDS.sleep(1);	
  isStop = true;	
}

代碼中通過一個變量isStop來控制線程是否停止。

通過變量控制和線程自帶的interrupt方法來中斷線程有什么區別呢?

如果一個線程調用了sleep方法,一直處于休眠狀態,通過變量控制,還可以中斷線程么?大家可以思考一下。

此時只能使用線程提供的interrupt方法來中斷線程了。

public static void main(String[] args) throws InterruptedException {	
  Thread thread1 = new Thread() {	
      @Override	
      public void run() {	
          while (true) {	
              //休眠100秒	
              try {	
                  TimeUnit.SECONDS.sleep(100);	
              } catch (InterruptedException e) {	
                  e.printStackTrace();	
              }	
              System.out.println("我要退出了!");	
              break;	
          }	
      }	
  };	
  thread1.setName("thread1");	
  thread1.start();	
  TimeUnit.SECONDS.sleep(1);	
  thread1.interrupt();	
}

調用interrupt()方法之后,線程的sleep方法將會拋出 InterruptedException異常。

Thread thread1 = new Thread() {	
  @Override	
  public void run() {	
      while (true) {	
          //休眠100秒	
          try {	
              TimeUnit.SECONDS.sleep(100);	
          } catch (InterruptedException e) {	
              e.printStackTrace();	
          }	
          if (this.isInterrupted()) {	
              System.out.println("我要退出了!");	
              break;	
          }	
      }	
  }	
};

運行上面的代碼,發現程序無法終止。為什么?

代碼需要改為:

Thread thread1 = new Thread() {	
  @Override	
  public void run() {	
      while (true) {	
          //休眠100秒	
          try {	
              TimeUnit.SECONDS.sleep(100);	
          } catch (InterruptedException e) {	
              this.interrupt();	
              e.printStackTrace();	
          }	
          if (this.isInterrupted()) {	
              System.out.println("我要退出了!");	
              break;	
          }	
      }	
  }	
};

上面代碼可以終止。

注意:sleep方法由于中斷而拋出異常之后,線程的中斷標志會被清除(置為false),所以在異常中需要執行this.interrupt()方法,將中斷標志位置為true

 

等待(wait)和通知(notify)

為了支持多線程之間的協作,JDK提供了兩個非常重要的方法:等待wait()方法和通知notify()方法。這2個方法并不是在Thread類中的,而是在Object類中定義的。這意味著所有的對象都可以調用者兩個方法。

public final void wait() throws InterruptedException;	
public final native void notify();

當在一個對象實例上調用wait()方法后,當前線程就會在這個對象上等待。這是什么意思?比如在線程A中,調用了obj.wait()方法,那么線程A就會停止繼續執行,轉為等待狀態。等待到什么時候結束呢?線程A會一直等到其他線程調用obj.notify()方法為止,這時,obj對象成為了多個線程之間的有效通信手段。

那么wait()方法和notify()方法是如何工作的呢?如圖2.5展示了兩者的工作過程。如果一個線程調用了object.wait()方法,那么它就會進出object對象的等待隊列。這個隊列中,可能會有多個線程,因為系統可能運行多個線程同時等待某一個對象。當object.notify()方法被調用時,它就會從這個隊列中隨機選擇一個線程,并將其喚醒。這里希望大家注意一下,這個選擇是不公平的,并不是先等待線程就會優先被選擇,這個選擇完全是隨機的。

java高并發之線程的基本操作詳解

除notify()方法外,Object獨享還有一個nofiyAll()方法,它和notify()方法的功能類似,不同的是,它會喚醒在這個等待隊列中所有等待的線程,而不是隨機選擇一個。

這里強調一點,Object.wait()方法并不能隨便調用。它必須包含在對應的synchronize語句匯總,無論是wait()方法或者notify()方法都需要首先獲取目標獨享的一個監視器。圖2.6顯示了wait()方法和nofiy()方法的工作流程細節。其中T1和T2表示兩個線程。T1在正確執行wait()方法錢,必須獲得object對象的監視器。而wait()方法在執行后,會釋放這個監視器。這樣做的目的是使其他等待在object對象上的線程不至于因為T1的休眠而全部無法正常執行。

線程T2在notify()方法調用前,也必須獲得object對象的監視器。所幸,此時T1已經釋放了這個監視器,因此,T2可以順利獲得object對象的監視器。接著,T2執行了notify()方法嘗試喚醒一個等待線程,這里假設喚醒了T1。T1在被喚醒后,要做的第一件事并不是執行后續代碼,而是要嘗試重新獲得object對象的監視器,而這個監視器也正是T1在wait()方法執行前所持有的那個。如果暫時無法獲得,則T1還必須等待這個監視器。當監視器順利獲得后,T1才可以在真正意義上繼續執行。

java高并發之線程的基本操作詳解

給大家上個例子:

package com.itsoku.chat01;	
/**	
* <b>description</b>:<br>	
* <b>time</b>:2019/7/12 17:18 <br>	
* <b>author</b>:微信公眾號:路人甲Java,專注于java技術分享(帶你玩轉 爬蟲、分布式事務、異步消息服務、任務調度、分庫分表、大數據等),喜歡請關注!	
*/	
public class Demo06 {	
  static Object object = new Object();	
  public static class T1 extends Thread {	
      @Override	
      public void run() {	
          synchronized (object) {	
              System.out.println(System.currentTimeMillis() + ":T1 start!");	
              try {	
                  System.out.println(System.currentTimeMillis() + ":T1 wait for object");	
                  object.wait();	
              } catch (InterruptedException e) {	
                  e.printStackTrace();	
              }	
              System.out.println(System.currentTimeMillis() + ":T1 end!");	
          }	
      }	
  }	
  public static class T2 extends Thread {	
      @Override	
      public void run() {	
          synchronized (object) {	
              System.out.println(System.currentTimeMillis() + ":T2 start,notify one thread! ");	
              object.notify();	
              System.out.println(System.currentTimeMillis() + ":T2 end!");	
              try {	
                  Thread.sleep(2000);	
              } catch (InterruptedException e) {	
                  e.printStackTrace();	
              }	
          }	
      }	
  }	
  public static void main(String[] args) throws InterruptedException {	
      new T1().start();	
      new T2().start();	
  }	
}

運行結果:

1562934497212:T1 start!	
1562934497212:T1 wait for object	
1562934497212:T2 start,notify one thread! 	
1562934497212:T2 end!	
1562934499213:T1 end!

注意下打印結果,T2調用notify方法之后,T1并不能立即繼續執行,而是要等待T2釋放objec投遞鎖之后,T1重新成功獲取鎖后,才能繼續執行。因此最后2行日志相差了2秒(因為T2調用notify方法后休眠了2秒)。

注意:Object.wait()方法和Thread.sleeep()方法都可以讓現場等待若干時間。除wait()方法可以被喚醒外,另外一個主要的區別就是wait()方法會釋放目標對象的鎖,而Thread.sleep()方法不會釋放鎖。

再給大家講解一下wait(),notify(),notifyAll(),加深一下理解:

可以這么理解,obj對象上有2個隊列,如圖1,q1:等待隊列,q2:準備獲取鎖的隊列;兩個隊列都為空。

java高并發之線程的基本操作詳解

obj.wait()過程:

synchronize(obj){	
  obj.wait();	
}

假如有3個線程,t1、t2、t3同時執行上面代碼,t1、t2、t3會進入q2隊列,如圖2,進入q2的隊列的這些線程才有資格去爭搶obj的鎖,假設t1爭搶到了,那么t2、t3機型在q2中等待著獲取鎖,t1進入代碼塊執行wait()方法,此時t1會進入q1隊列,然后系統會通知q2隊列中的t2、t3去爭搶obj的鎖,搶到之后過程如t1的過程。最后t1、t2、t3都進入了q1隊列,如圖3。

java高并發之線程的基本操作詳解

java高并發之線程的基本操作詳解

上面過程之后,又來了線程t4執行了notify()方法,如下:**

synchronize(obj){	
  obj.notify();	
}

t4會獲取到obj的鎖,然后執行notify()方法,系統會從q1隊列中隨機取一個線程,將其加入到q2隊列,假如t2運氣比較好,被隨機到了,然后t2進入了q2隊列,如圖4,進入q2的隊列的鎖才有資格爭搶obj的鎖,t4線程執行完畢之后,會釋放obj的鎖,此時隊列q2中的t2會獲取到obj的鎖,然后繼續執行,執行完畢之后,q1中包含t1、t3,q2隊列為空,如圖5

java高并發之線程的基本操作詳解

java高并發之線程的基本操作詳解

接著又來了個t5隊列,執行了notifyAll()方法,如下:

synchronize(obj){	
  obj.notifyAll();	
}

2.調用obj.wait()方法,當前線程會加入隊列queue1,然后會釋放obj對象的鎖

t5會獲取到obj的鎖,然后執行notifyAll()方法,系統會將隊列q1中的線程都移到q2中,如圖6,t5線程執行完畢之后,會釋放obj的鎖,此時隊列q2中的t1、t3會爭搶obj的鎖,爭搶到的繼續執行,未增強到的帶鎖釋放之后,系統會通知q2中的線程繼續爭搶索,然后繼續執行,最后兩個隊列中都為空了。

java高并發之線程的基本操作詳解

 

掛起(suspend)和繼續執行(resume)線程

Thread類中還有2個方法,即線程掛起(suspend)和繼續執行(resume),這2個操作是一對相反的操作,被掛起的線程,必須要等到resume()方法操作后,才能繼續執行。系統中已經標注著2個方法過時了,不推薦使用。

系統不推薦使用suspend()方法去掛起線程是因為suspend()方法導致線程暫停的同時,并不會釋放任何鎖資源。此時,其他任何線程想要訪問被它占用的鎖時,都會被牽連,導致無法正常運行(如圖2.7所示)。直到在對應的線程上進行了resume()方法操作,被掛起的線程才能繼續,從而其他所有阻塞在相關鎖上的線程也可以繼續執行。但是,如果resume()方法操作意外地在suspend()方法前就被執行了,那么被掛起的線程可能很難有機會被繼續執行了。并且,更嚴重的是:它所占用的鎖不會被釋放,因此可能會導致整個系統工作不正常。而且,對于被掛起的線程,從它線程的狀態上看,居然還是Runnable狀態,這也會影響我們隊系統當前狀態的判斷。

java高并發之線程的基本操作詳解

上個例子:

/**	
* <b>description</b>:<br>	
* <b>time</b>:2019/7/12 17:18 <br>	
* <b>author</b>:微信公眾號:路人甲Java,專注于java技術分享(帶你玩轉 爬蟲、分布式事務、異步消息服務、任務調度、分庫分表、大數據等),喜歡請關注!	
*/	
public class Demo07 {	
  static Object object = new Object();	
  public static class T1 extends Thread {	
      public T1(String name) {	
          super(name);	
      }	
      @Override	
      public void run() {	
          synchronized (object) {	
              System.out.println("in " + this.getName());	
              Thread.currentThread().suspend();	
          }	
      }	
  }	
  public static void main(String[] args) throws InterruptedException {	
      T1 t1 = new T1("t1");	
      t1.start();	
      Thread.sleep(100);	
      T1 t2 = new T1("t2");	
      t2.start();	
      t1.resume();	
      t2.resume();	
      t1.join();	
      t2.join();	
  }	
}

運行代碼輸出:

in t1
in t2

我們會發現程序不會結束,線程t2被掛起了,導致程序無法結束,使用jstack命令查看線程堆棧信息可以看到:

"t2" #13 prio=5 os_prio=0 tid=0x000000002796c000 nid=0xa3c runnable [0x000000002867f000]	
 java.lang.Thread.State: RUNNABLE	
      at java.lang.Thread.suspend0(Native Method)	
      at java.lang.Thread.suspend(Thread.java:1029)	
      at com.itsoku.chat01.Demo07$T1.run(Demo07.java:20)	
      - locked <0x0000000717372fc0> (a java.lang.Object)

發現t2線程在suspend0處被掛起了,t2的狀態竟然還是RUNNABLE狀態,線程明明被掛起了,狀態還是運行中容易導致我們隊當前系統進行誤判,代碼中已經調用resume()方法了,但是由于時間先后順序的緣故,resume并沒有生效,這導致了t2永遠滴被掛起了,并且永遠占用了object的鎖,這對于系統來說可能是致命的。

 

等待線程結束(join)和謙讓(yeild)

很多時候,一個線程的輸入可能非常依賴于另外一個或者多個線程的輸出,此時,這個線程就需要等待依賴的線程執行完畢,才能繼續執行。jdk提供了join()操作來實現這個功能。如下所示,顯示了2個join()方法:

public final void join() throws InterruptedException;	
public final synchronized void join(long millis) throws InterruptedException;

第1個方法表示無限等待,它會一直只是當前線程。知道目標線程執行完畢。

第2個方法有個參數,用于指定等待時間,如果超過了給定的時間目標線程還在執行,當前線程也會停止等待,而繼續往下執行。

比如:線程T1需要等待T2、T3完成之后才能繼續執行,那么在T1線程中需要分別調用T2和T3的join()方法。

上個示例:

/**	
* <b>description</b>:<br>	
* <b>time</b>:2019/7/12 17:18 <br>	
* <b>author</b>:微信公眾號:路人甲Java,專注于java技術分享(帶你玩轉 爬蟲、分布式事務、異步消息服務、任務調度、分庫分表、大數據等),喜歡請關注!	
*/	
public class Demo08 {	
  static int num = 0;	
  public static class T1 extends Thread {	
      public T1(String name) {	
          super(name);	
      }	
      @Override	
      public void run() {	
          System.out.println(System.currentTimeMillis() + ",start " + this.getName());	
          for (int i = 0; i < 10; i++) {	
              num++;	
              try {	
                  Thread.sleep(200);	
              } catch (InterruptedException e) {	
                  e.printStackTrace();	
              }	
          }	
          System.out.println(System.currentTimeMillis() + ",end " + this.getName());	
      }	
  }	
  public static void main(String[] args) throws InterruptedException {	
      T1 t1 = new T1("t1");	
      t1.start();	
      t1.join();	
      System.out.println(System.currentTimeMillis() + ",num = " + num);	
  }	
}

執行結果:

1562939889129,start t1	
1562939891134,end t1	
1562939891134,num = 10

num的結果為10,1、3行的時間戳相差2秒左右,說明主線程等待t1完成之后才繼續執行的。

另外一個方法是Thread.yield(),他的定義如下:

public static native void yield();

yield是謙讓的意思,這是一個靜態方法,一旦執行,它會讓當前線程出讓CPU,但需要注意的是,出讓CPU并不是說不讓當前線程執行了,當前線程在出讓CPU后,還會進行CPU資源的爭奪,但是能否再搶到CPU的執行權就不一定了。因此,對Thread.yield()方法的調用好像就是在說:我已經完成了一些主要的工作,我可以休息一下了,可以讓CPU給其他線程一些工作機會了。

如果覺得一個線程不太重要,或者優先級比較低,而又擔心此線程會過多的占用CPU資源,那么可以在適當的時候調用一下Thread.yield()方法,給與其他線程更多的機會。

 

總結

1.創建線程的2中方式:繼承Thread類;實現Runnable接口

2.啟動線程:調用線程的start()方法

3.終止線程:調用線程的stop()方法,方法已過時,建議不要使用

4.線程中斷相關的方法:調用線程實例interrupt()方法將中斷標志置為true;使用線程實例方法isInterrupted()獲取中斷標志;調用Thread的靜態方法interrupted()獲取線程是否被中斷,此方法調用之后會清除中斷標志(將中斷標志置為false了)

5.wait、notify、notifyAll方法,這塊比較難理解,可以回過頭去再理理

6.線程掛起使用線程實例方法suspend(),恢復線程使用線程實例方法resume(),這2個方法都過時了,不建議使用

7.等待線程結束:調用線程實例方法join()

8.出讓cpu資源:調用線程靜態方法yeild()

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注服務器之家的更多內容!

原文鏈接:https://itsoku.blog.csdn.net/article/details/100036273

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: porno日本大学生高清 | 国产免费色视频 | 四虎影院在线 | 男女男在线精品网站免费观看 | 非洲黑人xxxxxbbbbb | 成人高辣h视频一区二区在线观看 | 精品国产乱码久久久久久免费 | 日韩成人在线网站 | 俄罗斯女人与公拘i交酡 | 白丝萝莉喷水 | 7777色鬼xxxx欧美色夫 | 四虎永久免费地址在线观看 | tk白嫩玉足脚心vk | 国产a免费观看 | 高h喷水荡肉爽文np肉色文 | 视频国产精品 | 日本高h| 欧美日韩在线一区二区三区 | avove全部视频在线观看 | 视频一区二区三区在线 | bdsm中国精品调教 | 紧身裙女教师miad711在线 | 国产精品高清视亚洲一区二区 | 亚洲男人的天堂在线 | 亚洲另类第一页 | 成人私人影院在线版 | 男人机机桶女人机机 | 精品一区二区三区高清免费观看 | 99热免费在线观看 | 日韩一区国产二区欧美三 | 俄罗斯妈妈235 | 999任你躁在线精品免费不卡 | 天色综合 | 涩色网| 四虎影视4hu最新地址在线884 | 日韩亚洲欧美一区二区三区 | 精品性影院一区二区三区内射 | 国产亚洲精品91 | 久久免费看少妇高潮A片JA | 国产成人一区二区三区在线视频 | 国产日韩欧美成人 |