Java-java線程:兩個實現方法,死鎖現象,線程通訊,線程停止等

  1. 前言,前段時間一直在研究JavaWeb方向,最近閒下來,回過頭來看看線程,又有不一樣的見解。
package sh.thread;
public class ThreadDemo1 extends Thread{
    //1.自定義線程要執行的目標代碼
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("線程1:"+i);
        }
    }
    public static void main(String[] args){
        //2.創建自定義線程類的對象
        ThreadDemo1 T1 = new ThreadDemo1();
        //3.調用start方法啓動線程
        T1.start();
        for(int i=0;i<5;i++){//main主線程
            System.out.println("線程2:"+i);
        }   
    }
}
/*
 * 1.線程基礎知識:
 *   進程:正在執行的程序稱作爲一個進程。 進程負責了內存空間的劃分。
 *   線程:線程在一個進程中 負責了代碼的執行,就是進程中一個執行路徑。
 *   多線程:在一個進程中有多個線程(多個不同路徑),同時在執行不同的任務。
 * 
 * 2.多線程的好處:
 *   1.解決了一個進程能同時執行多個任務的問題。(高併發點擊網頁必須的!厲害吧,騷年!)
 *   2.提高了資源的利用率。
 * 
 * 3.多線程的弊端:
 *   1.增加cpu的的負擔。
 *   2.降低了一個進程中線程的執行概率。
 *   3.引發了線程安全問題。
 *   3.出現了死鎖現象。
 * 
 * 4.創建線程的方式:
 *   方法一:
 *       1.自定義一個類繼承Thread類。
 *       2.重寫Thread類的run方法,把自定義線程的任務代碼寫在run方法中
 *       (重寫的目的:每個線程都有自己的任務代碼,jvm創建的主線程的任務代碼就是 
 *       main方法中的所有代碼,而自定義線程的任務代碼就寫在run方法中)
 *       3.創建Thread的子類對象,並且調用start方法開啓線程。
 *     注意:一個線程一旦開啓,那麼線程就會執行run方法中的代碼,run方法千萬不能直接調用,
 *          直接調用 就相當調用了一個普通的方法而並沒有開啓新的線程。
 * */
  1. 用上面的一個基礎線程,引出線程生命週期圖,嗯,非常清晰了。
    這裏寫圖片描述

  2. 線程的一些常用方法及註解

package sh.thread;

public class ThreadDemo2 extends Thread{
/*
 5. 線程常用的方法:
     Thread(String name)     初始化線程的名字
     setName(String name)    設置線程對象名
     getName()               返回線程的名字

     sleep()    靜態:在哪個線程內,就是那個線程             線程睡眠指定的毫秒數。 靜態的方法, 那個線程執行了sleep方法代碼那麼就是那個線程睡眠。

     currentThread()   靜態:同上      返回當前的線程對象,該方法是一個靜態的方法, 注意: 那個線程執行了currentThread()代碼就返回那個線程 的對象。

     getPriority()             返回當前線程對象的優先級   默認線程的優先級是5
     setPriority(int newPriority) 設置線程的優先級    雖然設置了線程的優先級,但是具體的實現取決於底層的操作系統的實現(最大的優先級是10 ,最小的1 , 默認是5)。
 */
    public ThreadDemo2(String name){
        super(name);//調用了Thread類的一個 參數的構造方法。
    }
    @Override
    public void run() {
        /*System.out.println("this:"+ this); //this對象與當前線程對象是同一個
        System.out.println("當前線程對象:" + Thread.currentThread()); */

        for (int i = 0; i < 100 ; i++) {//自定義線程目標代碼
            System.out.println(Thread.currentThread().getName()+":"+i);         
            /*try {
                Thread.sleep(100);  //爲什麼在這裏不能拋出異常,只能捕獲?? Thread類的run方法沒有拋出異常類型,所以子類不能拋出異常類型(子類不能超過父類)
            } catch (InterruptedException e) {
                e.printStackTrace();
            } */
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //創建了一個線程對象
        ThreadDemo2 d = new ThreadDemo2("狗剩哥");
        d.setPriority(10); //設置線程 的優先級。 優先級的數字越大,優先級越高  , 優先級的範圍是1~10
        d.start();

        for (int i = 0; i < 100 ; i++) {//主線程目標代碼
            System.out.println(Thread.currentThread().getName()+":"+i);
        }

        /*
        System.out.println("自定義線程的優先級:"+d.getPriority());  //線程的優先級默認是5
        System.out.println("主線程的優先級:"+Thread.currentThread().getPriority());

        d.start();
        d.setName("鐵蛋"); //setName設置線程的名字
        d.start(); //開啓線程 

        Thread mainThread = Thread.currentThread();
        System.out.println("主線程的名字:"+ mainThread.getName());
    */
    }
}

4 .好吧,經典的多線程賣票問題,當時懵逼的有木有!,現在看來,不過爾爾…

package sh.thread;
/*
 1. 需求: 模擬3個窗口同時在售50張 票 。

問題1 :爲什麼50張票被賣出了150次?

出現 的原因: 因爲num是非靜態的,非靜態的成員變量數據是在每個對象中都會維護一份數據的,三個線程對象就會有三份。

 2. 解決方案:把num票數共享出來給三個線程對象使用。使用static修飾。

問題2: 出現了線程安全問題 ?

 3. 線程 安全問題的解決方案:sun提供了線程同步機制讓我們解決這類問題的。

    java線程同步機制的方式:

        方式一:同步代碼塊

            同步代碼塊的格式:

                synchronized(鎖對象){
                    需要被同步的代碼...
                }

4. 同步代碼塊要注意事項:
        1. 任意的一個對象都可以做爲鎖對象。
        2. 在同步代碼塊中調用了sleep方法並不是釋放鎖對象的。
        3. 只有真正存在線程安全問題的時候才使用同步代碼塊,否則會降低效率的。
        4. 多線程操作的鎖 對象必須 是唯一共享 的。否則無效。           
5. 出現線程安全問題的根本原因:
    1. 存在兩個或者兩個以上 的線程對象,而且線程之間共享着一個資源。
    2. 有多個語句操作了共享資源。
 */

class SaleTicket extends Thread{
     static int num = 50;//票數  非靜態的成員變量,非靜態的成員變量數據是在每個對象中都會維護一份數據的。

     static Object o = new Object();//定義一個唯一共享的鎖對象
     public SaleTicket(String name) {
        super(name);
    }
    @Override
    public void run() {
        while(true){
            //同步代碼塊
            synchronized (o) {              
                if(num>0){
                    System.out.println(Thread.currentThread().getName()+"售出了第"+num+"號票");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num--;
                }else{
                    System.out.println("賣完了..");
                    break;
                }
            }
        }
    }   
} 

public class ThreadDemo3 {
    public static void main(String[] args) {
        //創建三個線程對象,模擬三個窗口
        SaleTicket thread1 = new SaleTicket("窗口1");
        SaleTicket thread2 = new SaleTicket("窗口2");
        SaleTicket thread3 = new SaleTicket("窗口3");
        //開啓線程售票
        thread1.start();
        thread2.start();
        thread3.start();    
    }
}

5.線程通訊: 一個線程完成了自己的任務時,要通知另外一個線程去完成另外一個任務.
例子:生產者-消費者

package sh.thread;

/*
線程通訊: 一個線程完成了自己的任務時,要通知另外一個線程去完成另外一個任務.

生產者與消費者

wait():  等待   如果線程執行了wait方法,那麼該線程會進入等待的狀態,等待狀態下的線程必須要被其他線程調用notify方法才能喚醒。
notify(): 喚醒    喚醒線程池等待線程其中的一個。
notifyAll() : 喚醒線程池所有等待 線程。

wait與notify方法要注意的事項:
    1. wait方法與notify方法是屬於Object對象 的。
    2. wait方法與notify方法必須要在同步代碼塊或者是同步函數中才能 使用。
    3. wait方法與notify方法必需要由鎖對象調用。

*/

//產品類
class Product{
    String name;
    double price;
    boolean flag = false; //判斷產品是否已生產,默認沒有生產完成。
}
//生產者
class Producter extends Thread{
    //1.引入產品類
    Product p;
    Producter(Product p){
        this.p = p;
    }
    @Override
    public void run() {
        int i = 0;
        while (true) {
            synchronized (p) {
                if(p.flag==false){
                    if(i%2==0){
                        p.name = "蘋果";
                        p.price = 6.8;
                    }else {
                        p.name = "橡膠";
                        p.price = 2.0;
                    }
                    System.out.println("生產者生產出來:"+p.name+" 價格:"+p.price);
                    p.flag = true;
                    i++;
                    p.notifyAll();//喚醒消費者去消費,鎖對象調用
                }else {
                    //已經生產了一個了,等待消費者先去消費
                    try {
                        p.wait(); //生產者等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }       
            }
        }

    }
}
//消費者
class Customer extends Thread{
    //引入產品類
    Product p;
    public Customer(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        while(true){
            synchronized (p) {
                if(p.flag){
                    System.out.println("消費者消費了:"+p.name+" 價格:"+p.price);
                    p.flag = false;//消費一下就停了
                    p.notifyAll();
                }else {
                    try {//停止消費
                        p.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }   
}
public class ThreadDemo5 {

    public static void main(String[] args){
        Product p = new Product();
        Producter producter = new Producter(p);
        Customer customer = new Customer(p);
        producter.start();
        customer.start();
    }
}

6.線程的停止的 兩個方法

package sh.thread;

/*
 * 線程的停止:
 *  1.停止一個線程  我們一般都會通過一個變量去控制的。
 *  2.如果需要停止一個處於等待狀態下的線程,那麼我們需要通過變量配合
 *  notify方法或者interrupt()來使用。
 *notify:是喚醒它,它運行完了,自然就停止了,必須在鎖對象內
 *interrupt:直接把它的等待狀態強制清除!
 * */
public class Thread6 extends Thread{
    boolean flag = true;//停止線程的標識
    public Thread6(String name){
        super(name);
    }
    //自定義一個一開始就等待的線程,來停掉它!
    @Override
    public synchronized void run() {
        while(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                System.out.println("已停止,且捕獲異常了");
            }
        }
    }

    public static void main(String[] args) {
        Thread6 t6 = new Thread6("小二");
        t6.start();//開始自定義線程

        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
            if(i==8){
                t6.flag = false;//讓t6線程跳出循環
                //停止線程方法1:喚醒它,自然停止
                /*synchronized (t6) {
                    t6.notify();
                }*/
                //停止線程方法2:interrupt強制清除它的等待狀態:會拋出一個異常
                t6.interrupt();
            }
        }
    }
}

7 . 守護線程詳解

package sh.thread;
/*
 守護線程(後臺線程):在一個進程中如果只剩下 了守護線程,那麼守護線程也會死亡。

 //如果一個線程隨着你的主線程存在就存在,隨着主線程消亡就消亡,那就可以將它設置爲後臺線程
 //守護線程:做下載更新包... 
 需求: 模擬QQ下載更新包。

 一個線程默認都不是守護線程。
 */
public class Thread7 extends Thread {

    public Thread7(String name){
        super(name);
    }

    @Override
    public void run() {
        for(int i = 1 ; i<=10 ; i++){
            System.out.println("更新包目前下載"+i+"%");
            if(i==10){
                System.out.println("更新包下載完畢,準備安裝..");
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        Thread7 d = new Thread7("後臺線程");
         d.setDaemon(true); //setDaemon() 設置線程是否爲守護線程,true爲守護線程, false爲非守護線程。
        // System.out.println("是守護線程嗎?"+ d.isDaemon()); //判斷線程是否爲守護線程。
         d.start();
         for(int i = 1 ; i<=10 ; i++){
 System.out.println(Thread.currentThread().getName()+":"+i);
         }
    }
}

8 . 線程中的 join方法

package cn.itcast.thread;
/*
 join方法。 加入
 */
//老媽
class  Mon extends Thread{

    public void run() {
        System.out.println("媽媽洗菜");
        System.out.println("媽媽切菜");
        System.out.println("媽媽準備炒菜,發現沒有醬油了..");
        //叫兒子去打醬油
        Son s= new Son();
        s.start();
        try {
            s.join();  //加入。 一個線程如果執行join語句,那麼就有新的線程加入,執行該語句的線程必須要讓步給新加入的線程先完成任務,然後才能繼續執行。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("媽媽繼續炒菜");
        System.out.println("全家一起吃飯..");     
    }
} 
class Son extends Thread{
    @Override
    public void run() {
        System.out.println("兒子下樓..");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("兒子一直往前走");
        System.out.println("兒子打完醬油了");
        System.out.println("上樓,把醬油給老媽");
    }
}
public class Demo8 {
    public static void main(String[] args) {
        Mon m = new Mon();
        m.start();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章