天天看點

多線程基礎學習目标一、程序與線程二、Java多線程實作三、多線程的常用操作方法四、線程的同步與死鎖volatile關鍵字總結

多線程程式設計

  • 學習目标
  • 一、程序與線程
  • 二、Java多線程實作
      • 範例:定義線程類
    • 2.1Runnable接口實作多線程
    • 2.2、Thread與Runnable 的差別
    • 2.3、Callable接口實作多線程
    • 2.4、多線程運作狀态
  • 三、多線程的常用操作方法
    • 3.1、線程的命名和取得
    • 3.2、線程休眠
    • 3.3、線程中斷
    • 3.4、線程強制執行
    • 3.5、線程禮讓
    • 3.6、線程的優先級
  • 四、線程的同步與死鎖
      • 4.1、線程同步引出
      • 4.2、線程同步處理
      • 4.3、線程死鎖
  • volatile關鍵字
  • 總結

學習目标

1、了解程序與線程的聯系與差別。
2、Java中兩種多線程的兩種實作方式及差別。
3、掌握線程的基本操作方法,實作線程的休眠禮讓等操作。
4、多線程同步與死鎖的概念。
5、synchronized ()同步實作操作。
6、了解Object類對多線程的支援。
7、了解線程生命周期。
           

一、程序與線程

程序是程式的一次動态執行過程。多程序操作同時運作多個程序。

二、Java多線程實作

Java.lang.Thread 是一個負責線程操作的類,任何類隻要繼承Thread類就可以成為一個線程的主類。

同時線程類中需要明确覆寫父類中的run()方法,當産生若幹個線程類對象,這些對象就會并發執行run()方法的代碼。

範例:定義線程類

public class JavaDemo2 {
    public static void main(String[] args) {
        new Mytheard("線程A").start();  //執行個體化線程并啟動
        new Mytheard("線程B").start();  //執行個體化線程并啟動
        new Mytheard("線程C").start();  //執行個體化線程并啟動

    }
}
class Mytheard extends Thread{  //線程的主體類
    private String name;        //成員屬性
    public Mytheard(String name){   //屬性初始化
        this.name = name;
    }
    @Override
    public void run(){     //方法覆寫:線程方法
        for (int x = 0;x < 10;x++){
            System.out.println(this.name+"運作,x="+x);
        }
    }
}
程式輸出結果:
線程B運作,x=0
線程A運作,x=0
線程C運作,x=0
線程A運作,x=1
線程B運作,x=1
剩下的略
           
發現多個線程之間彼此交替執行,是以每次執行結果不一樣,啟動線程必須依靠Thread類的start()方法執行,線程啟動後會預設調用run()方法。
           

2.1Runnable接口實作多線程

使用Thread類的确友善實作多線程,最大的缺點單繼承局限,也可以利用Runnable接口來實作多線程,

接口定義如下:

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

範例:通過Runnable接口實作多線程

public class JavaDemo3 {
    public static void main(String[] args) {
        Thread threadA = new Thread(new Mythaer("線程對象A"));
        Thread threadB = new Thread(new Mythaer("線程對象B"));
        Thread threadC = new Thread(new Mythaer("線程對象C"));
        threadA.start();
        threadB.start();
        threadC.start();
    }
}
class Mythaer implements Runnable {  //線程的主體類
    public String title;             //成員屬性
    public Mythaer(String title){    //屬性初始化
        this.title = title;
    }

    @Override
    public void run() {        //方法覆寫:線程方法
        for (int i = 0;i < 6;i++){
            System.out.println(this.title+"運作,x="+i);
        }
    }
}
程式輸出結果:
線程對象B運作,x=0
線程對象C運作,x=0
線程對象A運作,x=0
線程對象C運作,x=1
線程對象B運作,x=1
剩餘略
           

2.2、Thread與Runnable 的差別

現在Thread類和Runnable類接口都可以以同一功能來實作多線程,從Java的實際開發而言,肯定使用Runnable接口,因為可以有效避免單繼承的局限,

Thread類的定義:

public class Thread extends Object implements Runnable {}
           

範例:并發資源通路

public class JavaDemo4 {
    public static void main(String[] args) {
        Mytherd mytherd = new Mytherd();
        new Thread(mytherd).start();  //線程1
        new Thread(mytherd).start();  //線程2
        new Thread(mytherd).start();  //線程3

    }
}
class Mytherd implements Runnable{  //線程類主體
    private int ticket = 5;         //定義總票數
  
    @Override
    public void run() {             //線程主體的方法
        for (int i = 0;i < 10;i++){ //進行100次的買票處理
            if (this.ticket > 0){   //判斷是否有剩餘
                System.out.println("買票,ticket="+this.ticket--);
            }
        }
    }
}
程式輸出結果:
買票,ticket=4
買票,ticket=2
買票,ticket=5
買票,ticket=1
買票,ticket=3
           

2.3、Callable接口實作多線程

java.util.concurrent.Callable,接口定義:

}
@FunctionalInterface
public interface Callable<V>{
    public V call() throws Exception;
}
           

Callable接口定義可以設定一個泛型,call()方法的傳回資料類型,可以避免向下轉型帶來的安全隐患。

範例:定義線程主體類以及傳回值

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class JavaDemo5 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(new Mythred());
        new Thread(task).start();
        System.out.println("縣城傳回資料"+task.get());
    }
}
class Mythred implements Callable<String>{ //定義主體類

    @Override
    public String call() throws Exception {  //線程執行方法
        for (int i = 0; i < 10;i++){
            System.out.println("線程執行,i="+i);
        }
        return "Java,1";   //傳回結果
    }
}
程式輸出結果:
線程執行,i=0
線程執行,i=1
線程執行,i=2
線程執行,i=3
線程執行,i=4
線程執行,i=5
線程執行,i=6
線程執行,i=7
線程執行,i=8
線程執行,i=9
縣城傳回資料Java,1
           

2.4、多線程運作狀态

想要運作多線程必須在主線程中建立新的線程對象。任意線程一般具有5中狀态:建立、就緒、運作、阻塞、終止

建立:新建立了一個線程對象。準備好了一個多線程的對象(沒有調用start()方法 )

就緒:調用了start()方法, 線程即進入就緒狀态。處于就緒狀态的線程,隻是說明此線程已經做好了準備,
随時等待CPU排程執行(擷取CPU的使用權),并不是說執行了t.start()此線程立即就會執行;

運作:執行run()方法(當CPU開始排程處于就緒狀态的線程時,此時線程才得以真正執行,即進入到運作狀态。
注:就緒狀态是進入到運作狀态的唯一入口,也就是說,線程要想進入運作狀态執行,首先必須處于就緒狀态中;)

阻塞:處于運作狀态中的線程由于某種原因,暫時放棄對CPU的使用權,停止執行,
此時進入阻塞狀态,直到其進入到就緒狀态,才有機會再次被CPU調用以進入到運作狀态。

終止:線程 run ()、 main () 方法執行結束,或者因異常退出了 run ()方法,
		則該線程結束生命周期。死亡的線程不可再次複生。
           

三、多線程的常用操作方法

3.1、線程的命名和取得

線程本身屬于不可見的運作狀态,每次操作的時間無法預料,唯一依靠的就是線程名稱。

方法 類型 描述
public Thread(Runnable target,String name) 構造 執行個體化對象名稱,接收Runnable接口子類對象
public final void seyName (Stirng name) 普通 設定線程名字
public final String getName() 普通 取得線程名字
取得目前線程對象:public static Thread currentThread()
           

範例:觀察線程的命名操作

public class JavaDemo6 {
    public static void main(String[] args) {
        Mythread mythread = new Mythread();   
        new Thread(mythread,"線程A").start();//設定線程名字
        new Thread(mythread).start();   //未設定線程名字
        new Thread(mythread,"線程B").start();
    }
}
class Mythread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());   //目前線程名稱
    }
}
程式運作結果:
線程A
線程B
Thread-0
           

依據運作結果發現如果沒有設定名字,系統會自動配置設定名字,形式“Thread-xxx”的方式出現

3.2、線程休眠

某一個線程可以暫緩執行,在Thread類中中定義的休眠方法如下

線程休眠
           
方法 類型 描述
public static void sleep(long millis) throws InterruptedException 普通 設定線程休眠的毫秒數,時間一到自動喚醒
public static void sleep(long millis,int nanos) throws InterruptedException 普通 設定線程休眠的毫秒數和納秒數,時間一到自動喚醒
線上程休眠的時候有可能會産生中斷異常InterruptedException,中斷異常屬于Exception的子類,
程式必須強制性進行該異常的捕獲與處理。
           

範例:線程休眠

public class JavaDemo {
    public static void main(String[] args) {
        Runnable runnable = () ->{
            for (int x = 0 ; x < 10; x++){      //Runnable接口執行個體
                System.out.println(Thread.currentThread().getName()+"、x="+x);

                try {
                    Thread.sleep(1000);  //暫緩1000毫秒(1秒)
                }catch (Exception e){   //強制性異常處理
                    e.printStackTrace();
                }
            }
        };
        for (int num = 0;num < 5;num++){
            new Thread(runnable,"線程對象"+num).start();   //啟動線程
        }
    }
}
程式輸出結果:
一直運作
           

3.3、線程中斷

線程中斷
           
方法 類型 描述
public boolean isInterrupted() 普通 判斷線程是否被中斷
public void interrupt 普通 中斷線程執行

範例:線程中斷

public class JavaDemo7 {
    public static void main(String[] args) throws Exception{
        Thread thread = new Thread(() -> {
            System.out.println("準備睡覺10秒,不許打擾我");
            try {
                Thread.sleep(10000);
                System.out.println("睡醒了,開始工作和學習!");
            } catch (Exception e) {
                System.out.println("被打擾,生氣");
            }
        });
        thread.start();      //啟動線程
        Thread.sleep(1000);   //保證比子線程先運作一秒
        if (!thread.isInterrupted()){    //該線程中斷
            System.out.println("打斷你睡覺");
            thread.interrupt();     //中斷執行
        }
    }
}
程式輸出結果:
準備睡覺10秒,不許打擾我
打斷你睡覺
被打擾,生氣

           

3.4、線程強制執行

在多線程并發執行中每一個線程對象都會交替執行,如果此時某個線程對象需要優先執行完成,
則可以設定為強制執行,等執行完畢後其他線程在繼續執行。Thread類定義的強制執行方法如下:
線程強制執行:public final void join() throws InterruptedException
           

範例:線程強制執行

/**
 * @ClassName : JavaDemo8  //類名
 * @Description : 線程強制執行 //描述
 * @Author : ***  //作者
 * @Date: 2021-07-21 09:20 //日期
 */

public class JavaDemo8 {
    public static void main(String[] args) throws Exception{
        Thread thread = Thread.currentThread();    //獲得主線程
        Thread thread1 = new Thread(()->{
           for (int i = 0;i < 10;i++){
               if (i==3){    //設定強制執行條件
                   try {
                       thread.join();   //強制執行任務
                   }catch (Exception e){
                       e.printStackTrace();
                   }
               }
               try {
                   Thread.sleep(100);   //延緩執行
               }catch (Exception e){
                   e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName()+"執行、i="+i);
           }
        },"玩耍的線程");
        thread1.start();
        for (int i = 0;i < 10;i++){    //主線程
            Thread.sleep(100);
            System.out.println("number="+i);
        }
    }
}
程式輸出結果:
number=0
玩耍的線程執行、i=0
number=1
玩耍的線程執行、i=1
number=2
玩耍的線程執行、i=2
number=3
number=4
number=5
number=6
number=7
number=8
number=9
玩耍的線程執行、i=3
玩耍的線程執行、i=4
玩耍的線程執行、i=5
玩耍的線程執行、i=6
玩耍的線程執行、i=7
玩耍的線程執行、i=8
玩耍的線程執行、i=9

           
本程式啟動了兩個線程:main線程和子線程,在不滿足強制執行條件時,兩個線程交替執行,
而當滿足了強制執行條件時在主線程執行完畢後在繼續執行子線程。
           

3.5、線程禮讓

線程禮讓是指當滿足某些條件,可以将目前的排程讓給其他線程執行,自己在等下次排程在執行
線程禮讓:public static void yield()
           

範例:線程禮讓

/**
 * @ClassName : JavaDemo9  //類名
 * @Description : 線程禮讓 //描述
 * @Author : ***  //作者
 * @Date: 2021-07-21 09:53 //日期
 */

public class JavaDemo9 {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(()->{
           for (int i = 0; i< 100;i++){
               if (i%3 == 0){
                   Thread.yield();   //線程禮讓
                   System.out.println("線程禮讓"+Thread.currentThread().getName());
               }
               try {
                   Thread.sleep(100);
               }catch (Exception e){
                   e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName()+"執行、i="+i);
           }
        },"線程");
        thread.start();
        for (int i = 0;i < 100;i++){
            Thread.sleep(100);
            System.out.println("number="+i);
        }
    }
}
程式輸出結果:
線程禮讓線程
number=0
線程執行、i=0
number=1
線程執行、i=1
線程執行、i=2
線程禮讓線程
number=2
number=3
剩餘略
           

3.6、線程的優先級

在Java中優先級越高約有可能被執行

方法或常量 類型 描述
public static final int MAX_PRIORITY 常量 最高優先級,數值為10
public static final int MIN_PRIORITY 常量 中等優先級,數值為5
public static final int NORM_PRIORITY 常量 最低優先級,數值為1
public final void setPriority(int newPriority) 普通 設定線程優先級
public final int getPriority(); 普通 取得線程優先級

四、線程的同步與死鎖

4.1、線程同步引出

線程同步是指若幹個線程對象并進行資源通路實作的資源處理的保護操作。
           

範例:3個線程買3張票

/**
 * @ClassName : JavaDemo10  //類名
 * @Description : 賣票操作 //描述
 * @Author : ***  //作者
 * @Date: 2021-07-23 09:19 //日期
 */

public class JavaDemo10 {
    public static void main(String[] args) throws Exception{
        Mythread1 mythread1 = new Mythread1();
        new Thread(mythread1,"售票員A").start();  //開啟線程
        new Thread(mythread1,"售票員B").start();
        new Thread(mythread1,"售票員C").start();
    }
}
class Mythread1 implements Runnable{   //定義線程執行類
    private int tickt = 3;    //定義票數三張

    @Override
    public void run() {
        while (true){
            if (this.tickt > 0 ){
                try {
                    Thread.sleep(100);  //開啟延遲
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"買票,tickt"+this.tickt--);
            }else {
                System.out.println("******票賣完了******");
                break;     //跳出循環
            }
        }
    }
}
程式輸出結果:
售票員A買票,tickt1
售票員C買票,tickt2
******票賣完了******
售票員B買票,tickt3
******票賣完了******
******票賣完了******
           

4.2、線程同步處理

Java中提供有synchronized關鍵字以實作同步處理,同步的關鍵是要為代碼加上"鎖",而對于鎖的操作程式有兩種:同步代碼塊、同步方法。

同步代碼塊是指使用synchronized關鍵字定義的代碼塊,需要設定一個同步對象,可以選擇this

範例:使用同步代碼塊

package JavaDemo11;

/**
 * @ClassName : synchronized1  //類名
 * @Description : 同步代碼塊 //描述
 * @Author : ***  //作者
 * @Date: 2021-07-23 09:52 //日期
 */

public class synchronized1 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        new Thread(myThread,"售票員A").start();  //開啟線程
        new Thread(myThread,"售票員B").start();
        new Thread(myThread,"售票員C").start();


    }
}
class MyThread implements Runnable{
    private int ticket = 3;   //總票數為3

    @Override
    public void run() {
        while (true){
            synchronized (this){     //同步代碼塊
                if (this.ticket > 0){
                    try {
                        Thread.sleep(100);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"買票,tickt"+this.ticket--);
                } else {
                    System.out.println("******票賣光了******");
                    break;
                }
            }
        }
    }
}
程式輸出結果:
售票員A買票,tickt3
售票員A買票,tickt2
售票員B買票,tickt1
******票賣光了******
******票賣光了******
******票賣光了******
           

範例:使用同步方法

package JavaDemo12;

/**
 * @ClassName : synchronized1  //類名
 * @Description : 同步方法 //描述
 * @Author : ***  //作者
 * @Date: 2021-07-23 10:13 //日期
 */

public class synchronized1 {
    public static void main(String[] args) {
        Mythread1 mythread1 = new Mythread1();
        new Thread(mythread1,"售票員A").start();
        new Thread(mythread1,"售票員B").start();
        new Thread(mythread1,"售票員C").start();

    }
}
class Mythread1 implements Runnable{
    private int ticket = 3;

    @Override
    public void run() {
        while (this.sale()){  //調運同步方法
            ;
        }
    }
    public synchronized boolean sale(){
        if (this.ticket > 0){
            try {
                Thread.sleep(100);
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"買票,tickt"+this.ticket--);
            return true;
        }else {
            System.out.println("******票買光了******");
            return false;
        }
    }
}
程式輸出結果:
售票員A買票,tickt3
售票員A買票,tickt2
售票員C買票,tickt1
******票買光了******
******票買光了******
******票買光了******
           

本程式将需要進行線程同步處理的操作封裝在sale()方法中,當多個線程并發通路時可以保證資料操作的正确性。

4.3、線程死鎖

所謂的死鎖,是指兩個線程都在等待對方先完成,造成了程式的停滞狀态。一般程式的死鎖都是在程式運作出現。

範例:觀察線程鎖死

package JavaDemo13;

/**
 * @ClassName : Book  //類名
 * @Description : 線程鎖死 //描述
 * @Author : ***  //作者
 * @Date: 2021-07-23 10:40 //日期
 */

class Book {
    public synchronized void tell(Painting paint){
        System.out.println("Java");
        paint.get();
    }
    public synchronized void get(){
        System.out.println("not");
    }
}
class Painting{
    public synchronized void tell(Book book){
        System.out.println("Pythen");
        book.get();
    }
    public synchronized void get(){
        System.out.println("HEllo World");
    }
}
class DeabLock implements Runnable{
    private Book book = new Book();
    private Painting painting = new Painting();
    public DeabLock(){
        new Thread(this).start();
        book.tell(painting);
    }

    @Override
    public void run() {
        painting.tell(book);
    }
    public static void main(String[] args){
        new DeabLock();
    }
}
程式輸出結果:
Java
Pythen
***程式處于互相等待狀态,後續代碼不執行***、
           

volatile關鍵字

範例:使用volatile關鍵字定義變量

package JavaDemo14;

/**
 * @ClassName : volatile2  //類名
 * @Description :  //描述
 * @Author : ***  //作者
 * @Date: 2021-07-23 10:53 //日期
 */

public class volatile2 {
    public static void main(String[] args) {
        Mythrev mythrev = new Mythrev();
        new Thread(mythrev,"售票員A").start();
        new Thread(mythrev,"售票員B").start();
        new Thread(mythrev,"售票員C").start();

    }
}
class Mythrev implements Runnable{
    private volatile int tickt = 3;   //直接記憶體操作

    @Override
    public void run() {
        synchronized (this){
            while (this.tickt > 0){
                try {
                    Thread.sleep(100);    //延遲模拟
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"買票,tickt"+this.tickt--);
            }
        }
    }
}
程式輸出結果:
售票員A買票,tickt3
售票員A買票,tickt2
售票員A買票,tickt1
           

總結

1、線程(Thread)是指程式的運作流程。“多線程”的機制可以同時運作多個程式塊。是程式運作的效率塊。
2、如果類裡激活線程,必須先做好下面兩項:
	1.1、此類必須繼承Thread類或者實作Runnable接口。
	1.2、線程的處理必須覆寫run()方法
3、每個線程有五個狀态:建立、就緒、運作、阻塞、終止。
4、Thread類裡的sleep()方法可以來控制線程的休眠狀态,休眠時間要在sleep()裡的參數。
5、使用synchronized 關鍵字來進行資源的同步處理,在進行同步處理時需要防範死鎖的産生。
6、volattile關鍵字并不是描述同步操作,避免了副本建立與資料同步處理。