1. 程式人生 > 資訊 >英偉達:將與 Arm 合作推出 PC 平臺產品

英偉達:將與 Arm 合作推出 PC 平臺產品

什麼是執行緒?

​ 執行緒是比程序更小的執行單位,是程式執行的基本執行單元。合理地使用執行緒是減少開發和維護成本

的必要條件,甚至能夠改善複雜應用程式的效能。

執行緒與程序的區別
  1. 每個程序都有獨立的程式碼和資料空間(程序上下文),程序間的切換會有較大在開銷;

  2. 執行緒可以看成是輕量級的程序,同一類執行緒共享程式碼和資料空間,每個執行緒有獨立的執行棧和程式計數器(PC),執行緒切換開銷小;

  3. 多程序:在作業系統中能同時執行多個任務(程式);

  4. 多執行緒:在同一應用程式中有多個順序流同時執行。

    所謂的多執行緒就是一個程序在執行的過程中可以產生多個同時存在、同時執行的執行緒,多執行緒機制可以合理利用資源,提高程式執行效率

實現Java多執行緒的方式:

  1. 繼承Thread類

  2. 實現Runnable介面

方法一:實現Thread類
  1. 首先繼承Thread類

  2. 例項化一個變數

  3. 給出一個構造方法

  4. 重寫Three中的run方法

  5. 給出主執行緒執行方法

  package com.gxy.java.demo;
/**
* 實現Java多執行緒方法一: 繼承Thread類
*/
public class Thread extends java.lang.Thread {
//例項化一個變數
private String name;
//給出構造方法
public Thread(String name){
this.name = name;
}
//給出Java多執行緒實現的業務邏輯(重寫run方法)
@Override
public void run() {
synchronized (this){
for (int i = 0; i < 5; i++) {
System.out.println(name + "運行了,i = " + i);
}
}
}
//給出主執行緒main執行方法
public static void main(String[] args) {
Thread t1 = new Thread("執行緒A");
Thread t2 = new Thread("執行緒B");
t1.start();
t2.start();
}
}
方法二:繼承Runnable介面
  1. 繼承runnable方法

  2. 重寫run方法

  3. 測試方法(步驟同上)

    啟動多執行緒必須依靠Thread類實現

  package com.gxy.java.demo;

//實現Runnable介面
public class Demo2 implements Runnable {
//例項化一個變數
private String name;
//給出構造方法
public Demo2(String name){
this.name = name;
}
//定義邏輯
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "被執行,當前i = " + i);
}
}
//給出主執行緒執行方法
public static void main(String[] args) {
//例項化runnable
Runnable a = new Demo2("A");
Runnable b = new Demo2("B");
//使用thread中的start方法執行
new Thread(a).start();
new Thread(b).start();
}
}
Thread類和Runnable介面的區別
  1. 繼承了Thread的類不適合多個執行緒共享資源

  2. 而實現了Runnable介面可以方便的實現資源共享

實現多視窗售票功能

方法一:使用Thread類

  package com.gxy.java.demo;
//繼承Thread類
public class Demo3 extends Thread {
//假設現在有五張票
private Integer ticket = 5;
//私有變數
private String name;
//構造方法
public Demo3(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
//確認現在有票 才可以出售
if(ticket > 0){
System.out.println(
Thread.currentThread().getName() + "買票:ticket = " + ticket--);
}
}
}
public static void main(String[] args) {
Demo3 a = new Demo3("A");
Demo3 b = new Demo3("B");
Demo3 c = new Demo3("C");
a.start();
b.start();
c.start();
}
}

執行結果:

​ 啟動程式中的三個執行緒會各自賣出五張票,因為建立了三個執行緒物件,就想到與建立了三個資源每個執行緒都有五張票需要賣出,獨立處理各自的資源沒有達到資源共享的目的

方法二:實現Runnable介面

  package com.gxy.java.demo;
//實現介面
public class Demo4 implements Runnable{
private Integer ticket = 5;
public void run() {
for (int i = 0; i < 5; i++) {
//確認現在有票 才可以出售
if(ticket > 0){
System.out.println("賣票:ticket = " + ticket--);
}
}
}
public static void main(String[] args) {
//例項化
Demo4 d = new Demo4();
//創造執行緒去執行方法
new Thread(d).start();
new Thread(d).start();
new Thread(d).start();
}
}

執行結果:

​ 由執行結果可知,通過 Runnable 介面啟動了三個執行緒,共賣出五張票,即 ticket 屬性被所有的執行緒

物件共享。原因在於每個執行緒呼叫的是同一個 Demo4 物件中的 start( )方法。

實現Runnable介面和繼承Thread類在實現多執行緒中的優勢

  • 適合多個具有相同程式程式碼的執行緒處理同一資源。

  • 可以避免 Java 的單繼承特徵帶來的侷限性。

  • 程式碼能夠被多個執行緒共享且與資料獨立存在,從而增加了程式的健壯性。


執行緒操作方法
  1. 啟動方法(開始執行執行緒)

  new Demo3().start(); //開始執行
  1. 執行執行緒

  new Demo3().run();//執行
  1. 獲取執行緒名稱

  Thread.currentThread().getName() //獲取當前執行緒的名稱
  1. 設定執行緒的睡眠時間 單位是毫秒

  try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
  1. 暫停當前的執行緒去執行別的執行緒(執行緒禮讓)

  Thread.yield();
  1. 執行緒的優先順序

  Thread t1 = new Thread("執行緒A");
Thread t2 = new Thread("執行緒B");
t1.setPriority(); //設定執行緒的優先順序
//優先順序有三個 min低(1-4) norm中(5) max高(6-10)
t1.setPriority(MAX_PRIORITY); //--> 優先順序最高
t2.setPriority(MIN_PRIORITY); //--> 優先順序最低
t1.setPriority(NORM_PRIORITY); //--> 優先順序中等
//執行緒將根據其優先順序的高低決定執行順序
//並非執行緒優先順序越高就一定優先被執行,最終還是由 CPU 的排程來決定最先執行的執行緒

注意事項:

1. 執行緒最終啟動呼叫的還是run()方法

​ 執行緒啟動時呼叫了 start( )方法,系統通過 start( )方法呼叫 run( )方法,執行執行緒主體

​ 一段程式碼若需要在新執行緒中執行,該段程式碼就必須放在類的 run( )方法中,同時一個執行緒類物件只允許呼叫一次 start( )方法,多次呼叫將產生異常

2.sleep(睡眠) 方法和 yield(禮讓)方法的區別

​ 1) sleep( )方法使當前執行緒進入停滯狀態,所以執行 sleep( )方法的執行緒在指定時間內肯定不會被執行yield( )方法只是使當前執行緒重新回到可執行(就緒)狀態,所以執行 yield( )方法的執行緒有可能在進入到可執行狀態後馬上又被執行。

​ 2) sleep( )方法可以使優先級別低的執行緒得到執行的機會,當然也可以讓同優先順序和高優先順序的執行緒有執行的機會;yield( )方法只能使同優先順序的執行緒得到執行的機會。

同步和死鎖
執行緒同步:

什麼是執行緒同步:同步是指同一時間段內只能執行一個執行緒,其他執行緒需要等待此執行緒完成後才可繼續執行。同步可以解決執行緒中資源共享的安全問題,主要通過同步程式碼塊和同步方法兩種方式完成

方法一:同步程式碼塊

  
//語法格式:Synchronized(同步物件){ //需要同步的程式碼 }
//示例程式碼
@Override
public void run() {
synchronized (this){
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "運行了,i = " + i);
}
}
}

方法二:同步方法

  
//語法格式:synchronized 方法返回型別 方法名稱(引數列表){ }
@Override
public void run() {
//呼叫 this指代當前
this.test();
}
//同步方法
public synchronized void test(){
synchronized (this){
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "運行了,i = " + i);
}
}
}
死鎖

造成死鎖的原因:多個執行緒共享同一資源需要進行同步,以保證資源操作的完整性,但過多的同步可能產生死鎖

出現死鎖需要滿足的條件(必須同時滿足,缺少一個就不能出現死鎖):

​ 1. 互斥條件:一個資源每次只能被一個執行緒使用。

​ 2. 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放。

​ 3. 不可剝奪條件:程序已獲得的資源,在未使用完之前,不能強行剝奪

​ 4. 迴圈等待條件:若干程序之間形成一種頭尾相接的迴圈等待資源關係

解決措施:

​ 1) 打破互斥條件,我們需要允許程序同時訪問某些資源,這種方法受制於實際場景,不太容易實現條件;

​ 2) 打破不可搶佔條件,這樣需要允許程序強行從佔有者那裡奪取某些資源,或者簡單一點理解,佔有資源的程序不能再申請佔有其他資源,必須釋放手上的資源之後才能發起申請,這個其實也很難找到適用場景;

​ 3) 程序在執行前申請得到所有的資源,否則該程序不能進入準備執行狀態。這個方法看似有點用處,但是它的缺點是可能導致資源利用率和程序併發性降低

​ 4) 避免出現資源申請環路,即對資源事先分類編號,按號分配。這種方式可以有效提高資源的利用率和系統吞吐量,但是增加了系統開銷,增大了程序對資源的佔用時間。

執行緒的生命週期概括

生命週期包括的五種狀態:建立、就緒、執行、阻塞和終止

詳細解析

  1. 建立狀態

    程式中使用構造方法建立執行緒物件後,新執行緒物件即處於建立狀態。

    執行緒此時已經具有相應的記憶體空間和其他資源,但不可執行

  2. 就緒狀態

    執行緒物件建立後呼叫 start()方法啟動執行緒,即進入就緒狀態。

    就緒狀態下的執行緒進入執行緒佇列,等待 CPU 呼叫

  3. 執行狀態

    執行緒獲得 CPU 服務後即處於執行狀態,此時將自動呼叫執行緒物件的 run()方法。run()方法定義了該執行緒的具體操作和實現功能。

    需要注意的是執行狀態下的執行緒呼叫 yield()方法後,將從執行狀態返回至就緒狀

  4. 阻塞狀態

    執行狀態的執行緒呼叫 sleep()、wait()等方法後將進入阻塞狀態。

    執行緒阻塞條件解除後,執行緒再次轉入就緒狀態。

​ 5.終止(死亡狀態)

​ 當執行緒執行 run()方法完畢後處於終止狀態(又稱死亡狀態)處於終止狀態下的執行緒不具有繼續執行的能力。

綜合示例

  
package com.gxy.java.demo;

public class Demo5 extends Thread {
private static Demo5 demo1 = null;
@Override
public void run() {
try {
//執行狀態
System.out.println("demo1" + demo1.getState());
Thread.sleep(1000);

//阻塞狀態
System.out.println("demo1" + demo1.getState());

} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//建立狀態
demo1 = new Demo5();
System.out.println("demo1" + demo1.getState());
//就緒狀態
demo1.start();
}
}
本章總結

1、 一個程序包括由作業系統分配的記憶體空間,包含一個或多個執行緒。一個執行緒不能獨立的存在,它必須24

是程序的一部分。一個程序一直執行,直到所有的非守護執行緒都結束執行後才能結束。

2、 Java 中實現多執行緒主要有兩種方式:繼承 Thread 類和實現 Runnable 介面。

3、 執行緒的生命週期:新建狀態、就緒狀態、執行狀態、阻塞狀態、死亡狀態。

4、 執行緒的同步就是指多個執行緒在同一時間段內只能有一個執行緒執行指定程式碼,其他執行緒要等待此執行緒完成之後才能繼續執行。

5、 執行緒進行同步主要有兩種方式:

​ ➢ 同步程式碼塊:synchronized(要同步的物件){要同步的操作},即有 synchronized 關鍵字修飾的語句塊。 被該關鍵字修飾的語句塊會自動被加上內建鎖,從而實現同步。

➢ 同步方法:public synchronized void method(){要同步的操作}即有 synchronized 關鍵字修飾的方法。

補充:觀察繼承了Thread類的多執行緒和實現Runnable介面的多執行緒的區別

首先寫一個繼承Thread類的類和實現了Runnable介面的類

  
package com.gxy.java.demo;
//繼承了Thread的類test1
public class Test1 extends Thread {
private Integer count = 0;
@Override
public void run() {
System.out.println(++count+"");
}
}
  package com.gxy.java.demo;
//實現Runnable介面的類 test2
public class Test2 implements Runnable {
private Integer num = 0;
public void run() {
System.out.println(++num+"");
}
}

然後寫一個測試主類

  
package com.gxy.java.demo;

/**
* 觀察繼承了Thread類的多執行緒和實現Runnable介面的多執行緒的區別
*/
public class Demo7 {
public static void main(String[] args) {
System.out.println("繼承了Thread類的多執行緒:\t");
for (int i = 0; i < 10; i++) {
Test1 test1 = new Test1();
//啟動繼承了Thread類的多執行緒
test1.start();
}
try {
//睡眠三秒 讓上面的執行完畢
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------------------");
System.out.println("實現了Runnable的多執行緒:\t");

Test2 test2 = new Test2();
for (int i = 0; i < 10; i++) {
new Thread(test2).start();
}

}
}

結論:在十次迴圈中

​ 繼承了Thread類的多執行緒執行結果每次都是1,相當於每次執行都會重值資料-->不會共享資料

​ 而實現Runnable介面的多執行緒會在上次執行結果上面進行累加 --> 實現了資料共享