1. 程式人生 > >JAVA執行緒(簡單)

JAVA執行緒(簡單)

多執行緒:就是應用程式有多條執行路徑
    執行緒:是程序的執行單元,執行路徑。
        如果一個應用程式只有一條只有執行路徑,那麼,該程式就是單執行緒程式
        如果一個應用程式有多條執行路徑,那麼,該程式就是多執行緒
    程序:正在執行的應用程式,
        每個程序具有獨立的空間。
        我們目前的作業系統都是支援多程序的。
    舉例:360,迅雷
        一個人吃一桌飯:單程序單執行緒
        多個人吃一桌飯:單程序多執行緒
    多執行緒的好處:效率高一點
    多執行緒的問題:
        多個執行緒針對同一個資源,導致資料會有安全問題,資源衝突
我們怎麼實現多執行緒程式?
    假設JAVA沒有提供,怎麼辦?
    我們要想實現多執行緒,首先要去建立一個程序,然後將程序拆分成多個執行緒
    問題來了:程序是由作業系統根據應用程式的執行產生的,也就是,我想要建立程序,我們得去操作作業系統的資源,但是,java程式是不能直接操作作業系統的資源的,那麼,我們直接通過java語言做這件事情是實現不了的。
    所以,java就提供了執行緒的類供我們使用,
    但是,底層肯定是C
或者C++去完成這件事情,這個操作被包裝了,我們看不多,把CC++的程式按照java的模式去編寫,然後用java去呼叫了CC++的方法。 接下來,我們只能依賴Thread實現。

方法一:通過繼承Thread類

通過檢視API,可以知道,多執行緒程式的模擬有兩種方法
    A:繼承Thread類
    B:實現Runnable介面 

繼承Thread類的步驟:
    A:自定義類實現Thread類
    B:在自定義類中重寫run()方法
        為什麼要重寫run()方法呢?
        因為run()方法中的封裝的程式碼才是可以被執行緒執行的。
    C
:建立自定義類的物件 D:啟動執行緒並使用 如何啟動? 執行緒物件建立後,將來呼叫的肯定是run方法裡面的程式碼 但是,必須通過start()方法啟動 run()和start()的區別 run()封裝了被執行緒執行的程式碼 start():讓執行緒啟動,並由jvm呼叫run()方法。 如何獲取到執行時的執行緒的名字? getName()//返回執行緒名稱,Thread類中的方法 設定執行緒的名字: 一:自定義名字: setName(String name)//更改執行緒名稱 二:Thread(String
name):父類的帶參構造。 自定義類呼叫上面的方法: public myThread(String name){ super(name); }

方法二:通過實現Runnable介面

實現Runnable介面的方式:
    A:自定義類MyRunnable實現Runnable介面
    B:重寫run()方法
    C:建立自定義類的物件
    D:建立Thread類的物件,將MyRunnable類的物件作為構造引數傳遞
    E:??呼叫Thread的物件呼叫start方法。

針對實現介面的這種方法,沒有辦法獲取名稱,所以java就提供了一個靜態方法。獲取當前正在執行的執行緒物件
public static Thread currentThread();
然後通過執行緒物件去獲取物件的名稱。

設定執行緒名字:
A:setName()
B:Thread(Runnable target, String name)通過建構函式引數直接設定名字。

問題1:繼承Thread類和實現Runnable有什麼區別?

因為類只能單繼承!!!!!!!!!!!!!

比如:
我有一個fu類,一個Zi類,Zi類已經繼承了父類,要想實現多繼承,怎麼辦?

     實現Runnable介面。

問題2:繼承Thread類的方式,子類本身就是一個執行緒類,實現Runnable介面的方式,具體類本身是不是一個執行緒例項呢?

不是!因為如果是的話,就可以直接呼叫setName方法去設定執行緒的名字了。

應用程式的執行:

CPU在多個應用程式之間進行著高效的切換執行,這個切換的時間很短,短到你感覺不到,所以,我們覺得我們的程式是在同時併發的執行, 但是準確的說:CPU在某個時間點上只能有一個程式在執行

多執行緒的好處

開多執行緒的目的是提高CPU的使用率,進而來提高程式的效率。

面試題:執行緒的生命週期

新建:建立執行緒物件
就緒:準備隨時執行,具備執行資格,沒有執行權
執行:執行Run中程式碼
死亡:run結束
阻塞:在執行過程中出現意外情況,,當情況唄解決後,就回到就緒狀態。

程式碼一:Thread

package com.thread.ticket.thread;

/**
 * 我有一百張票,放在4個視窗賣
 * 
 * 執行緒是共享程序的資源
 * 一個JAVA程式是 一個程序
 *  棧:每個執行緒都有自己的一份
 *  堆:共享的
 *  方法區:共享的
 *  
 * 問題:
 *  A:把int ticket = 100 ;放到run()方法中,每次呼叫run()都會重新定義100張票。
 *  每個執行緒都有自己的棧空間,那麼,每個執行緒物件都會擁有自己的100張票
 *  B:把private int tickets =100 ;定義到了成員變數的位置,還是會有問題
 *    按照正常的思緒,應該是沒有問題了的,但是還是出現了問題。
 *    原因很簡單,因為我們建立了四個物件,而普通的成員變數是每個物件所特有的。
 *    也就是說,每個執行緒物件還是擁有自己的100張票。
 *  我們的目的是:這四個執行緒物件共同擁有100張票,請問怎麼能夠實現?
 *      加static
 */
public class TicketDemo {
    public static void main(String[] args) {
        Ticket t = new Ticket("視窗1");
        Ticket t1 = new Ticket("視窗2");
        Ticket t2 = new Ticket("視窗3");
        Ticket t3 = new Ticket("視窗4");
        t.start();
        t1.start();
        t2.start();
        t3.start();

    }
}



package com.thread.ticket.thread;

public class Ticket extends Thread {
    private static int tickets = 100;

    public Ticket() {
    }

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

    @Override
    public void run() {

        while (true) {
            if (tickets > 0) {
                System.out.println(getName() + "正在出售第" + (tickets--) + "張票");
            }
        }
    }
}


程式碼二:Runnable優化

package com.thread.ticket.runnable;

/**
 * 我有一百張票,放在4個視窗賣
 * 通過Runnable改進賣票程式
 */
public class TicketDemo {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Thread t = new Thread(t1, "視窗1");
        Thread t4 = new Thread(t1, "視窗2");
        Thread t2 = new Thread(t1, "視窗3");
        Thread t3 = new Thread(t1, "視窗4");
        t.start();
        t4.start();
        t2.start();
        t3.start();

    }
}


package com.thread.ticket.runnable;

public class Ticket implements Runnable {
    private static int tickets = 100;

    @Override
    public void run() {

        while (true) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");
            }
        }
    }
}

程式碼三:執行緒安全問題分析與討論

package com.thread.ticket1;

/**
 * 我有一百張票,放在4個視窗賣
 * 通過Runnable改進賣票程式
 * //模擬正常的現象,我讓執行緒稍微等一下。
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 *問題:出現相同的票賣了兩次,以及出現負數票。
 *A:相同票賣了兩次:
 *  CPU的每一次執行,必須是一個原子性的操作,這個操作是不能再分隔的,
 *      int i= 10;  其實是兩個原子性動作。
 *      這樣的話
 *      tickets-- 也不是一個原子性動作,可能在操作的中間部分,被其他的執行緒給執行了,
 *      這樣就會有相同的票執行了多次
 *  面試題:
 *      if(true) 
 *          int a=10;
 *      //if語句沒有寫大括號,預設情況下,只負責處理跟在其後面的一條語句,
 *      而:int a = 10;  其實不是一個語句;
 *      等價:
 *          int a;
 *          a=10; 
 *B:負票:
 *  因為執行緒的隨機性,
 */
public class TicketDemo {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Thread t = new Thread(t1, "視窗1");
        Thread t4 = new Thread(t1, "視窗2");
        Thread t2 = new Thread(t1, "視窗3");
        Thread t3 = new Thread(t1, "視窗4");
        t.start();
        t4.start();
        t2.start();
        t3.start();

    }
}



package com.thread.ticket1;

public class Ticket implements Runnable {
    private static int tickets = 100;

    /*    @Override
        public void run() {
            while (true) {
                //t1,t2,t3,t4
                //tickets = 100;
                //假設t1搶到了CPU的執行權
                if (tickets > 0) {
                    //模擬正常的現象,我讓執行緒稍微等一下。
                    try {
                        Thread.sleep(100);//t1睡眠了。t2搶到了CPU的執行權,t2進來後,也睡眠了
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //t1醒過來,繼續執行
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");
                    //視窗1正在出售100張票
                    //tickets = 99 
                    //這僅僅是我們自己的理想狀況,但是實際情況並不是這個樣子的。
                    //實際情況是:每一次執行緒執行的程式應該是一個原子性的操作。也就是這個操作是不能再分割的。
                    //tickets--這個動作有tickets-1=99和tickets=99兩個動作
                    //這裡的話,至少是兩個動作,那麼在-1的動作執行時候,並沒有給tickets重新賦值。
                    //t2醒過來了,這個時候tickets還是100,所以:這個時候,視窗二正在執行100張票。
                }
            }
        }*/
    @Override
    public void run() {
        while (true) {
            //t1,t2,t3,t4
            //tickets = 100;
            //假設t1搶到了CPU的執行權
            //t2搶到了
            //t3搶到了
            //t4搶到了
            if (tickets > 0) {
                //t1睡了
                //t2睡了
                //t3睡了
                //t4睡了
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");
                /**
                 * t1醒過來了
                 * 視窗1正在出售第1張票 tickets = 0 
                 * t2醒過來了
                 * 視窗2正在出售第0張票 tickets = -1
                 * t3醒過來了
                 * 視窗3正在出售第-1張票 tickets = -2
                 * t4醒過來了
                 * 視窗4正在出售第-2張票 tickets = -3
                 */

            }
        }
    }
}

程式碼4:執行緒安全的問題解決

package com.thread.ticket2;

/**
 * 這裡解決上面程式碼出現負票的方法:
 * 
 * 首先:多執行緒出現安全問題的原因:
 *  A:是多執行緒程式;
 *  B:有共享資料
 *  C:針對共享資料有多條語句操作
 *  
 * 解決:
 *  只需要把多執行緒環境中,操作共享資料的操作給變成單執行緒的就沒有問題了。
 *  java針對這種情況,就提供了同步技術,,同步程式碼塊
 *  格式:
 *      synchronized(物件){
 *          需要被同步的程式碼;
 *      }
 *    A:物件?
 *      如果不知道用哪個物件,就使用Object,
 *    B:需要被同步的程式碼塊?
 *      哪些程式碼導致出現了問題,就把哪些程式碼同步起來。
 *      
 *    哪些程式碼會出現問題呢?
 *      有共享資料
 *      針對共享資料有多條語句操作
 *    加入同步後發現還有問題,為什麼??
 *       while (true) {
            synchronized (new Object()) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");

                }
            }

        }
 *      同步程式碼塊中的物件針對多個執行緒必須是同一個,
 *      其實這個物件被稱為同步鎖物件
 * 
 */
public class TicketDemo {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Thread t = new Thread(t1, "視窗1");
        Thread t4 = new Thread(t1, "視窗2");
        Thread t2 = new Thread(t1, "視窗3");
        Thread t3 = new Thread(t1, "視窗4");
        t.start();
        t4.start();
        t2.start();
        t3.start();

    }
}



package com.thread.ticket2;

/**
 * 類似火車上廁所的例子
 * 
 * @author yuliyang
 * @version $Id: Ticket.java, v 0.1 2016年11月30日 下午10:02:32 yuliyang Exp $
 */
public class Ticket implements Runnable {
    private static int   tickets = 100;
    private final Object b       = new Object();

    @Override
    public void run() {
        while (true) {
            /**
             * t1,t2,t3,t4
             * 假設 ticket = 1
             * t1過來,看到synchronized就知道下面的程式碼被同步了。
             * 看到obj就知道這是這是鎖物件,假設t1進來前,obj這個鎖是開的狀態
             * 
             */
            synchronized (b) {
                /**
                 * t1進來後,就把鎖物件的狀態改為關的狀態。
                 */
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1睡著了。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");

                }
                //t1出來了,你們繼續搶,我也還可以搶。
            }

        }
    }
}