Java多線程(三)

Java多線程(一):
https://blog.csdn.net/Veer_c/article/details/103842078
Java多線程(二):
https://blog.csdn.net/Veer_c/article/details/103842263
Java多線程(三):
https://blog.csdn.net/Veer_c/article/details/103842317
Java多線程(四):
https://blog.csdn.net/Veer_c/article/details/103842602

線程中的一些方法

1.線程加入
public final void join()
等待該線程中止,其他線程才能繼續搶着執行,因爲線程執行的時候,每個線程都會搶佔CPU的執行權,所以我們可以利用此方法先讓一個線程執行完畢後,然後再去執行其他的線程。

package com.edu_01;
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"--"+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //創建三個線程
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();
        //給線程起名字
        mt1.setName("劉備");
        mt2.setName("曹操");
        mt3.setName("孫權");
        //開啓三個線程
        mt1.start();
        //接着讓mt1這個線程設置爲加入線程,其他線程就沒有搶佔cpu執行權的權利了,只能等待該線程執行完畢之後,才能開始搶佔
        try {
            mt1.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        mt2.start();
        mt3.start();
    }
}

2.線程禮讓
public static void yield():暫停當前正在執行的線程對象,並執行其他線程。
作用:讓線程間的執行更和諧一些,也就是讓線程搶佔CPU的概率相同一點。

package com.edu_02;
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.getName()+"---"+i);
            //實現線程禮讓
            Thread.yield();
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //創建兩個線程
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        //給線程設置名字
        mt1.setName("郭德綱");
        mt2.setName("周立波");
        //開啓線程
        mt1.start();
        mt2.start();
    }
}

3.線程死亡
public final void stop():直接殺死(即終止當前運行的線程,線程不會往下執行)
public void interrupt():直接殺死,在死前,還可以有遺言。(也就是說,線程還可以將該程序執行完)

package com.edu_03;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyThread extends Thread{
    @Override
    public void run() {
        //打印一下開始執行的時間
        System.out.println("開始時間:"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        //休眠10秒鐘
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            //e.printStackTrace();
            System.out.println("我被殺死了");
        }
        System.out.println("結束時間:"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
    }
}
public class Test {
    public static void main(String[] args) {
        //創建線程對象
        MyThread mt = new MyThread();
        //開啓線程對象
        mt.start();
        //在線程處於睡眠的過程中將他殺死
        try {
            Thread.sleep(3000);
            //殺死剛剛開啓的線程
            //調用stop()方法將線程直接殺死
            //mt.stop();//劃了一條橫線表示該方法已經過時,但是還可以使用
            //interrupt():直接殺死,在死前,還可以有遺言。
            mt.interrupt();//線程被殺死之後會將後面的代碼執行完畢之後,再死去
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

4.線程休眠
static void sleep(long millis) 線程睡一會,讓線程休眠,一定會醒來的。

線程的生命週期

1.新建:線程的對象調用Start()方法
2.就緒:線程有cpu的執行權,但是沒有CPU執行資格,開始搶佔CPU的執行權
3.運行:搶佔到了CPU的執行權,開始執行。(如果被其他線程搶到了執行權,則會恢復到就緒狀態)
4.有可能阻塞:線程對象調用sleep方法,wait方法,在線程睡醒之後,或者被喚醒之後,就會恢復到就緒狀態,
5.死亡:線程調用stop方法,或者調用interrupt方法,或者在線程執行完run方法後。
在這裏插入圖片描述
再來張顏色不單調的。
在這裏插入圖片描述
下面對線程生命週期中的 7 種狀態做說明:
出生狀態:用戶在創建線程時所處的狀態,在用戶使用該線程實例調用 start() 方法之前,線程都處於出生狀態。
就緒狀態:也稱可執行狀態,當用戶調用 start() 方法之後,線程處於就緒狀態。
運行狀態:當線程得到系統資源後進入運行狀態。
等待狀態:當處於運行狀態下的線程調用 Thread 類的 wait() 方法時,該線程就會進入等待狀態。進入等待狀態的線程必須調用 Thread 類的 notify() 方法才能被喚醒。notifyAll() 方法是將所有處於等待狀態下的線程喚醒。
休眠狀態:當線程調用 Thread 類中的 sleep() 方法時,則會進入休眠狀態。
阻塞狀態:如果一個線程在運行狀態下發出輸入/輸出請求,該線程將進入阻塞狀態,在其等待輸入/輸出結束時,線程進入就緒狀態。對阻塞的線程來說,即使系統資源關閉,線程依然不能回到運行狀態。
死亡狀態:當線程的 run() 方法執行完畢,線程進入死亡狀態。

提示:一旦線程進入可執行狀態,它會在就緒狀態與運行狀態下輾轉,同時也可能進入等待狀態、休眠狀態、阻塞狀態或死亡狀態。

使線程處於就緒狀態有如下幾種方法。
調用 sleep() 方法。
調用 wait() 方法。
等待輸入和輸出完成。

當線程處於就緒狀態後,可以用如下幾種方法使線程再次進入運行狀態。
線程調用 notify() 方法。
線程調用 notifyAll() 方法。
線程調用 intermpt() 方法。
線程的休眠時間結束。
輸入或者輸出結束。

線程間通信(生產消費者問題):不同類型線程針對同一個資源的操作
1.系統不僅要賣票還要入票,每次生產一張票,就會賣出去一張票,稱爲單生產單消費問題。
2.不僅要賣肉夾饃還要生產肉夾饃,可以利用多個線程去生產肉夾饃,然後利用多個線程去出售,稱爲多生產多消費問題。

案例:以給學生設置和獲取姓名和年齡爲例,演示線程通信問題
線程間通訊:

資源:Student
設置數據線程:SetThread
獲取數據線程:GetThread
測試類:StudentDemo

package com.edu_04;
public class Student {
    String name;
    int age;
}
public class SetThread implements Runnable{
    private Student s;
    private int x=0;
    public SetThread(Student s){
        this.s = s;
    }
    @Override
    public void run() {
        //給學生對象設置姓名和年齡
        //Student s = new Student();
        while (true) {
            synchronized (s) {
                if (x%2==0) {
                    s.name = "劉嘉玲";
                    s.age = 50;
                }else {
                    s.name = "陳冠希";
                    s.age = 35;
                }
                x++;
            }
        }
    }
}
public class GetThread implements Runnable{
    private Student s;

    public GetThread(Student s){
        this.s = s;
    }
    @Override
    public void run() {
        //獲取線程,獲取學生對象的姓名和年齡
        //Student s = new Student();
        while (true) {
            synchronized (s) {  
                System.out.println(s.name+"--"+s.age);  
            }
        }
    }
}
public class StudentDemo {
    public static void main(String[] args) {
        //創建一個學生對象
        Student s = new Student();
        //創建設置和獲取線程,並開啓線程
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);
        //開啓線程
        t1.start();
        t2.start();
    }
}

問題1:控制檯出現的結果是:null—0
因爲我們在在創建對象的時候,setThread()和getThread()的對象不是同一個,所以在輸出的時候,因爲setThread()還沒有設置對象,所以我在傳遞參數的時候可以自己創建構造方法,讓這個參數一致
設置和獲取線程使用的學生資源不是同一個,把資源作爲構造參數傳遞即可。
問題2:
相同的數據出現了多次,CPU的一點點時間就足夠我們的程序執行很多次
數據出現了問題(數據安全問題)
a:是否是多線程環境 是
b:是否有共享數據 是
c:是否有多條語句操作共享數據 是
既然我們知道它是出現了數據安全問題,我們就應該來解決它。
如何解決呢?加鎖
問題3:加了鎖以後,數據還是有問題
A:多個線程都要加鎖
B:多個線程加的鎖必須是同一把鎖
因爲,線程在執行的時候,每個線程的都會有搶佔的隨機性,所以輸出的時候沒有那麼和諧,而且會出現錯誤的數據,出現線程不安全問題,所以我們要給線程加鎖,保證線程數據安全,我們可以用等待,喚醒機制保證數據的和諧

將上述代碼使用等待喚醒機制改進,實現禮讓效果

package com.edu_05;
public class Student {
    String name;
    int age;
    boolean flag;//在這裏可以作爲對象的一個標記,如果是false說明該對象沒有數據,如果是true說明該對象有數據
}
public class SetThread implements Runnable{
    private Student s;
    private int x = 0;

    public SetThread(Student s){
        this.s = s;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                //判斷該對象此時有沒有數據
                if (s.flag) {
                    //等待
                    try {
                        s.wait();//設置線程等待,釋放鎖s
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if (x%2==0) {
                    s.name = "劉嘉玲";
                    s.age = 50;
                }else {
                    s.name = "冠希";
                    s.age = 35;
                }
                x++;//x=1
                //此時對象有數據了
                s.flag = true;
                s.notify();//如果有等待的線程就喚醒,如果沒有等待的線程,則沒有任何效果
            }//在此時釋放鎖對象s
        }
    }
}
public class GetThread implements Runnable{
    private Student s;
    public GetThread(Student s){
        this.s = s;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                //判斷對象有沒有數據
                if (!s.flag) {
                    //等待設置線程給對象設置數據
                    try {
                        s.wait();//獲取線程處於等待狀態,釋放鎖對象s,在哪裏跌倒在哪裏爬起來
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                System.out.println(s.name+"--"+s.age);//劉嘉玲--50
                                                      //冠希--35
                                                      //劉嘉玲--50
                //當獲取線程從學生對象中獲取了數據之後,我們就默認他已經沒有數據了,此時我們應該
                //繼續讓設置線程繼續給學生對象設置信息
                s.flag = false; 
                s.notify();
            }
        }
    }
}
public class StudentDemo {
    public static void main(String[] args) {
        //創建學生對象
        Student s = new Student();
        //創建設置線程和獲取線程
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);
        //開啓線程
        t1.start();
        t2.start();
    }
}

將上述代碼繼續優化:可以將上述代碼封裝成方法
1.私有化Student類的成員變量
2.在類的內部提供設置和獲取的同步方法

public class Student {
    //塊編輯(alt+shift+a):在使用塊編輯的時候,一定要將輸入法切換到英文輸入法,不然會出問題
    private String name;
    private int age;
    private boolean flag;//在這裏可以作爲對象的一個標記,如果是false說明該對象沒有數據,如果是true說明該對象有數據
    //提供公共的方法設置信息
    public synchronized void setInfo(String name,int age){
        if (this.flag) {
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        //沒有值的話,在這裏給對對象設置數據
        this.name = name;
        this.age = age;
        //更改標記,喚醒獲取線程獲取數據
        this.flag = true;
        this.notify();
    }
    //提供公共的方法獲取信息
    public synchronized void getInfo(){
        if (!this.flag) {
            //沒有值
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        //有數據,取數據
        System.out.println(this.name+"--"+this.age);
        //取完數據之後,就沒有數據了
        this.flag = false;
        this.notify();
    }
}
public class SetThread implements Runnable{
    private Student s;
    private int x = 0;
    public SetThread(Student s){
        this.s = s;
    }
    @Override
    public void run() {
        while (true) {
                if (x%2==0) {
                    s.setInfo("劉嘉玲", 50);
                }else {
                    s.setInfo("陳冠希", 35);
                }
                x++;//x=1
            }
    }
}
public class GetThread implements Runnable{
    private Student s;
    public GetThread(Student s){
        this.s = s;
    }
    @Override
    public void run() {
        while (true) {
            s.getInfo();
        }
    }
}
public class StudentDemo {
    public static void main(String[] args) {
        //創建學生對象
        Student s = new Student();
        //創建設置線程和獲取線程
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);
        //開啓線程
        t1.start();
        t2.start();
    }
}

Java多線程(一):
https://blog.csdn.net/Veer_c/article/details/103842078
Java多線程(二):
https://blog.csdn.net/Veer_c/article/details/103842263
Java多線程(三):
https://blog.csdn.net/Veer_c/article/details/103842317
Java多線程(四):
https://blog.csdn.net/Veer_c/article/details/103842602

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章