Java基礎之 多線程

1、建立多線程程序的第一種方式: 繼承(extends) Thread類 

  Thread類的子類: MyThread java

    //1.建立一個Thread類的子類
public class MyThread extends Thread{
    //2.在Thread類的子類中重寫Thread類中的run方法,設置線程任務(開啓線程要作什麼?)
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println("run:"+i);
        }
    }
}

  主線程: MyThread 程序員

public class MainThread {
    public static void main(String[] args) {
        //3.建立Thread類的子類對象
        MyThread mt = new MyThread();
        //4.調用Thread類中的方法start方法,開啓新的線程,執行run方法
        mt.start();
for (int i = 0; i <20 ; i++) { System.out.println("main:"+i); } } }

結果:隨機性打印編程

main:0
run:0
main:1
run:1
main:2
run:2
main:3
run:3
main:4
main:5
。。。。

原理:安全

  一、JVM執行  MainThread類的main 方法時,知道OS開闢了一條main方法通向CPU的路徑。服務器

    這個路徑叫作main線程,主線程。CPU經過這個路徑能夠執行main方法。多線程

  二、JVM執行到  mt.start();時,開闢了一條通向CPU的新路徑來 執行run方法。併發

  三、對於CPU而言,就有了兩條執行的路徑,CPU就有了選擇權,咱們控制不了CPU,異步

    兩個線程,main線程和,新的 MyThread的新線程,一塊兒搶奪CPU的執行權ide

    (執行時間),誰搶到誰執行。函數

      屢次啓動一個線程是非法的。特別是當線程已經結束執行後,不能再從新啓動。

    java程序屬於搶佔式調度,優先級高的線程優先執行;同一個優先級,隨機執行

線程的內存解析圖:

2、建立多線程程序的第二種方式: 實現(implements) Runnable 類

  實現Runable接口的子類:MyRunbale
    //1.建立一個Runnable接口的實現類
public class MyRunnable implements Runnable {
    //2.在實現類中重寫Runnable接口的run方法,設置線程任務
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

  主線程: MyThread 

public class MainThread {
    public static void main(String[] args) {
        //3.建立一個Runnable接口的實現類對象
        MyRunnable run = new MyRunnable();
        //4.建立Thread類對象,構造方法中傳遞Runnable接口的實現類對象
        //Thread t = new Thread(run);//打印線程名稱
        Thread t = new Thread(run);//打印HelloWorld
        //5.調用Thread類中的start方法,開啓新的線程執行run方法
        t.start();

        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

結果:隨機性打印

main-->0
Thread-0-->0
main-->1
Thread-0-->1
main-->2
Thread-0-->2
main-->3
Thread-0-->3
main-->4
Thread-0-->4
main-->5
Thread-0-->5
main-->6
Thread-0-->6
main-->7
Thread-0-->7

原理和內存和實現Thread相識。
實現Runnable接口建立多線程程序的好處:
        1.避免了單繼承的侷限性
            一個類只能繼承一個類(一我的只能有一個親爹),類繼承了Thread類就不能繼承其餘的類
            實現了Runnable接口,還能夠繼承其餘的類,實現其餘的接口
        2.加強了程序的擴展性,下降了程序的耦合性(解耦)
            實現Runnable接口的方式,把設置線程任務和開啓新線程進行了分離(解耦)
            實現類中,重寫了run方法:用來設置線程任務
            建立Thread類對象,調用start方法:用來開啓新線程

 3、匿名內部類實現線程的建立

 匿名內部類做用:簡化代碼
把子類繼承父類,重寫父類的方法,建立子類對象合一步完成
把實現類實現類接口,重寫接口中的方法,建立實現類對象合成一步完成
匿名內部類的最終產物:子類/實現類對象,而這個類沒有名字

實現代碼:

/*
    匿名內部類方式實現線程的建立

    匿名:沒有名字
    內部類:寫在其餘類內部的類

    格式:
        new 父類/接口(){
            重複父類/接口中的方法
        };
 */
public class InnerClassThread {
    public static void main(String[] args) {
        //線程的父類是Thread
        // new MyThread().start();
        new Thread(){
            //重寫run方法,設置線程任務
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"NOT_Copy");
                }
            }
        }.start();

        //線程的接口Runnable
        //Runnable r = new RunnableImpl();//多態
        Runnable r = new Runnable(){
            //重寫run方法,設置線程任務
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"程序員");
                }
            }
        };
        new Thread(r).start();

        //簡化接口的方式
        new Thread(new Runnable(){
            //重寫run方法,設置線程任務
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"MWW");
                }
            }
        }).start();
    }
}

4、線程安全

  一、線程安全產生的緣由:

   一、單線程不會出現線程安全問題

   二、多個線程,沒有訪問共享資源,也不會產生線程安全問題

   三、多個線程,且訪問了共享資源,就會產生線程安全問題。

  代碼示例:

  線程實現類:

/*
    實現賣票案例
 */
public class RunnableImpl implements Runnable{
    //定義一個多個線程共享的票源
    private  int ticket = 100;
//設置線程任務:賣票 @Override public void run() { //使用死循環,讓賣票操做重複執行 while(true){ //先判斷票是否存在 if(ticket>0){ try { Thread.sleep(10); //提升安全問題出現的機率,讓程序睡眠 } catch (InterruptedException e) { e.printStackTrace(); } //票存在,賣票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票"); ticket--; } } } }

  MainThread類:

/*
    模擬賣票案例
    建立3個線程,同時開啓,對共享的票進行出售
 */
public class MainThread {
    public static void main(String[] args) {
            //建立Runnable接口的實現類對象
            RunnableImpl run = new RunnableImpl();
            //建立Thread類對象,構造方法中傳遞Runnable接口的實現類對象
            Thread t0 = new Thread(run);
            Thread t1 = new Thread(run);
            Thread t2 = new Thread(run);
            //調用start方法開啓多線程
            t0.start();
            t1.start();
            t2.start();
    }
}

結果:

Thread-1-->正在賣第100張票
Thread-0-->正在賣第99張票
Thread-2-->正在賣第98張票
Thread-1-->正在賣第97張票
Thread-0-->正在賣第96張票
Thread-2-->正在賣第95張票
Thread-1-->正在賣第94張票
Thread-0-->正在賣第94張票
Thread-2-->正在賣第92張票
Thread-1-->正在賣第91張票
Thread-0-->正在賣第91張票
Thread-2-->正在賣第89張票
Thread-0-->正在賣第88張票
Thread-1-->正在賣第88張票
Thread-2-->正在賣第86張票
Thread-0-->正在賣第85張票
Thread-1-->正在賣第85張票
Thread-2-->正在賣第83張票
Thread-1-->正在賣第82張票
Thread-0-->正在賣第82張票
Thread-2-->正在賣第80張票
Thread-0-->正在賣第79張票
Thread-1-->正在賣第79張票
Thread-2-->正在賣第77張票
Thread-1-->正在賣第76張票
Thread-0-->正在賣第76張票
Thread-2-->正在賣第74張票
Thread-1-->正在賣第73張票
Thread-0-->正在賣第73張票
Thread-2-->正在賣第71張票
Thread-1-->正在賣第70張票
Thread-0-->正在賣第70張票
Thread-2-->正在賣第68張票
Thread-0-->正在賣第67張票
Thread-1-->正在賣第67張票
Thread-2-->正在賣第65張票
Thread-0-->正在賣第64張票
Thread-1-->正在賣第64張票
Thread-2-->正在賣第62張票
Thread-1-->正在賣第61張票
Thread-0-->正在賣第61張票
Thread-2-->正在賣第59張票
Thread-0-->正在賣第58張票
Thread-1-->正在賣第58張票
Thread-2-->正在賣第56張票
Thread-1-->正在賣第55張票
Thread-0-->正在賣第55張票
Thread-2-->正在賣第53張票
Thread-1-->正在賣第52張票
Thread-0-->正在賣第52張票
Thread-2-->正在賣第50張票
Thread-0-->正在賣第49張票
Thread-1-->正在賣第49張票
Thread-2-->正在賣第47張票
Thread-0-->正在賣第46張票
Thread-1-->正在賣第46張票
Thread-2-->正在賣第44張票
Thread-1-->正在賣第43張票
Thread-0-->正在賣第42張票
Thread-2-->正在賣第41張票
Thread-2-->正在賣第40張票
Thread-1-->正在賣第40張票
Thread-0-->正在賣第40張票
Thread-1-->正在賣第37張票
Thread-2-->正在賣第37張票
Thread-0-->正在賣第37張票
Thread-0-->正在賣第34張票
Thread-1-->正在賣第34張票
Thread-2-->正在賣第34張票
Thread-1-->正在賣第31張票
Thread-2-->正在賣第31張票
Thread-0-->正在賣第31張票
Thread-1-->正在賣第28張票
Thread-0-->正在賣第28張票
Thread-2-->正在賣第28張票
Thread-2-->正在賣第25張票
Thread-1-->正在賣第25張票
Thread-0-->正在賣第25張票
Thread-1-->正在賣第22張票
Thread-0-->正在賣第22張票
Thread-2-->正在賣第22張票
Thread-1-->正在賣第19張票
Thread-0-->正在賣第19張票
Thread-2-->正在賣第19張票
Thread-0-->正在賣第16張票
Thread-1-->正在賣第16張票
Thread-2-->正在賣第16張票
Thread-1-->正在賣第13張票
Thread-0-->正在賣第13張票
Thread-2-->正在賣第13張票
Thread-0-->正在賣第10張票
Thread-1-->正在賣第10張票
Thread-2-->正在賣第10張票
Thread-0-->正在賣第7張票
Thread-1-->正在賣第7張票
Thread-2-->正在賣第7張票
Thread-2-->正在賣第4張票
Thread-1-->正在賣第4張票
Thread-0-->正在賣第4張票
Thread-0-->正在賣第1張票
Thread-1-->正在賣第0張票
Thread-2-->正在賣第-1張票

結果出現了賣重複的票,賣不存在的票。

二、線程安全問題的解決方案:

  1)、同步代碼塊:

/*
    格式:
        synchronized(鎖對象){
            可能會出現線程安全問題的代碼(訪問了共享數據的代碼)
        }

    注意:
        1.經過代碼塊中的鎖對象,可使用任意的對象
        2.可是必須保證多個線程使用的鎖對象是同一個
        3.鎖對象做用:
            把同步代碼塊鎖住,只讓一個線程在同步代碼塊中執行
 */

  修改線程實現類:

/*
    實現賣票案例
 */
public class RunnableImpl implements Runnable{
    //定義一個多個線程共享的票源
    private  int ticket = 100;
    //建立一個鎖對象
    Object object=new Object();
    //設置線程任務:賣票
    @Override
    public void run() {
        //使用死循環,讓賣票操做重複執行
        while(true){
            //同步代碼塊
            synchronized (object){
                //先判斷票是否存在
                if(ticket>0){
                    try {
                        Thread.sleep(10); //提升安全問題出現的機率,讓程序睡眠
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在,賣票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
                    ticket--;
                }
            }
        }
    }
}

將可能出現線程安全問題的代碼放到   synchronized (object){}  代碼塊中。

結果:

Thread-0-->正在賣第100張票
Thread-0-->正在賣第99張票
Thread-0-->正在賣第98張票
Thread-0-->正在賣第97張票
Thread-2-->正在賣第96張票
Thread-2-->正在賣第95張票
Thread-2-->正在賣第94張票
Thread-2-->正在賣第93張票
Thread-2-->正在賣第92張票
Thread-2-->正在賣第91張票
Thread-2-->正在賣第90張票
Thread-2-->正在賣第89張票
Thread-2-->正在賣第88張票
Thread-2-->正在賣第87張票
Thread-2-->正在賣第86張票
Thread-2-->正在賣第85張票
Thread-2-->正在賣第84張票
Thread-2-->正在賣第83張票
Thread-2-->正在賣第82張票
Thread-2-->正在賣第81張票
Thread-2-->正在賣第80張票
Thread-2-->正在賣第79張票
Thread-2-->正在賣第78張票
Thread-2-->正在賣第77張票
Thread-2-->正在賣第76張票
Thread-2-->正在賣第75張票
Thread-2-->正在賣第74張票
Thread-2-->正在賣第73張票
Thread-2-->正在賣第72張票
Thread-2-->正在賣第71張票
Thread-2-->正在賣第70張票
Thread-2-->正在賣第69張票
Thread-2-->正在賣第68張票
Thread-2-->正在賣第67張票
Thread-2-->正在賣第66張票
Thread-2-->正在賣第65張票
Thread-2-->正在賣第64張票
Thread-2-->正在賣第63張票
Thread-2-->正在賣第62張票
Thread-2-->正在賣第61張票
Thread-2-->正在賣第60張票
Thread-2-->正在賣第59張票
Thread-2-->正在賣第58張票
Thread-2-->正在賣第57張票
Thread-2-->正在賣第56張票
Thread-2-->正在賣第55張票
Thread-2-->正在賣第54張票
Thread-2-->正在賣第53張票
Thread-2-->正在賣第52張票
Thread-2-->正在賣第51張票
Thread-2-->正在賣第50張票
Thread-2-->正在賣第49張票
Thread-2-->正在賣第48張票
Thread-2-->正在賣第47張票
Thread-2-->正在賣第46張票
Thread-2-->正在賣第45張票
Thread-2-->正在賣第44張票
Thread-2-->正在賣第43張票
Thread-2-->正在賣第42張票
Thread-2-->正在賣第41張票
Thread-2-->正在賣第40張票
Thread-2-->正在賣第39張票
Thread-2-->正在賣第38張票
Thread-2-->正在賣第37張票
Thread-2-->正在賣第36張票
Thread-2-->正在賣第35張票
Thread-2-->正在賣第34張票
Thread-2-->正在賣第33張票
Thread-2-->正在賣第32張票
Thread-2-->正在賣第31張票
Thread-2-->正在賣第30張票
Thread-2-->正在賣第29張票
Thread-2-->正在賣第28張票
Thread-2-->正在賣第27張票
Thread-2-->正在賣第26張票
Thread-2-->正在賣第25張票
Thread-2-->正在賣第24張票
Thread-2-->正在賣第23張票
Thread-2-->正在賣第22張票
Thread-2-->正在賣第21張票
Thread-2-->正在賣第20張票
Thread-2-->正在賣第19張票
Thread-2-->正在賣第18張票
Thread-2-->正在賣第17張票
Thread-2-->正在賣第16張票
Thread-2-->正在賣第15張票
Thread-2-->正在賣第14張票
Thread-2-->正在賣第13張票
Thread-2-->正在賣第12張票
Thread-2-->正在賣第11張票
Thread-2-->正在賣第10張票
Thread-2-->正在賣第9張票
Thread-1-->正在賣第8張票
Thread-1-->正在賣第7張票
Thread-1-->正在賣第6張票
Thread-1-->正在賣第5張票
Thread-1-->正在賣第4張票
Thread-1-->正在賣第3張票
Thread-1-->正在賣第2張票
Thread-1-->正在賣第1張票

再也不出現重複票,和不存在的票了。

  實現原理:使用一個鎖對象,這個對象叫作同步鎖,也叫做對象監視器。

  上述代碼中三個線程搶佔CPU執行權,

  t0 搶到了CPUd 執行權,執行run方法,遇到synchronized代碼塊,

  這時候 t0 會檢查 synchronized 代碼塊是否有鎖對象,

  發現有就會獲取到鎖對象進入到同步代碼塊執行。

 

  t1 搶到了CPU的執行權,執行  run 方法,遇到 synchronized 代碼塊

  這時 t1會檢查synchronized代碼塊是否有鎖對象

  發現沒有 t1 就會進入到阻塞狀態,會一直等到 t0 線程歸還 鎖對象

  一直到 t0 線程執行完同步代碼,會把鎖對象歸還給同步代碼塊。

  t1 才能獲取到 鎖對象 進入到同步執行。

 

  總結:同步中的線程沒有執行完畢不會釋放鎖,沒有鎖也進不進同步代碼,

    這樣就保證了只有一個線程在同步中執行共享數據,保證了安全,

    程序頻繁的判斷鎖,獲取鎖,釋放鎖,效率會下降。

 

  2)、同步方法

  修改線程實現類:

/*
    實現賣票案例
 */
public class RunnableImpl implements Runnable{
    //定義一個多個線程共享的票源
    private  int ticket = 100;
    //建立一個鎖對象
    Object object=new Object();
    //設置線程任務:賣票
    @Override
    public void run() {
        //使用死循環,讓賣票操做重複執行
        while(true){
           payTicket();
        }
    }
    /*
        定義一個同步方法
        同步方法也會把方法內部的代碼鎖住
        只讓一個線程執行
        同步方法的對象是誰?
        就是實現類對象 new RunnableImpl()
     */
    public synchronized void payTicket(){
        //先判斷票是否存在
        if(ticket>0){
            try {
                Thread.sleep(10); //提升安全問題出現的機率,讓程序睡眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在,賣票 ticket--
            System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
            ticket--; }
    }
}

還能夠用靜態的同步代碼:

package DemoThread;
/*
    實現賣票案例
 */
public class RunnableImpl implements Runnable{
    //定義一個多個線程共享的票源
    private static int ticket = 100;
    //建立一個鎖對象
    Object object=new Object();
    //設置線程任務:賣票
    @Override
    public void run() {
        //使用死循環,讓賣票操做重複執行
        while(true){
            payTicketStatic();
        }
    }
    /*
        定義一個靜態的同步方法
        對象是誰?
        就是實現類對象 本類的class屬相 --> class文件對象(反射)
     */
    public static synchronized void payTicketStatic(){
        //先判斷票是否存在
        if(ticket>0){
            try {
                Thread.sleep(10); //提升安全問題出現的機率,讓程序睡眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在,賣票 ticket--
            System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
            ticket--;
        }
    }
}

將可能出現線程安全問題的代碼放到   synchronized 修飾的方法中。

結果:

Thread-0-->正在賣第100張票
Thread-2-->正在賣第99張票
Thread-2-->正在賣第98張票
Thread-2-->正在賣第97張票
Thread-2-->正在賣第96張票
Thread-2-->正在賣第95張票
Thread-2-->正在賣第94張票
Thread-2-->正在賣第93張票
Thread-2-->正在賣第92張票
Thread-2-->正在賣第91張票
Thread-2-->正在賣第90張票
Thread-2-->正在賣第89張票
Thread-2-->正在賣第88張票
Thread-2-->正在賣第87張票
Thread-2-->正在賣第86張票
Thread-2-->正在賣第85張票
Thread-2-->正在賣第84張票
Thread-2-->正在賣第83張票
Thread-2-->正在賣第82張票
Thread-2-->正在賣第81張票
Thread-2-->正在賣第80張票
Thread-2-->正在賣第79張票
Thread-2-->正在賣第78張票
Thread-2-->正在賣第77張票
Thread-2-->正在賣第76張票
Thread-2-->正在賣第75張票
Thread-2-->正在賣第74張票
Thread-2-->正在賣第73張票
Thread-2-->正在賣第72張票
Thread-2-->正在賣第71張票
Thread-2-->正在賣第70張票
Thread-2-->正在賣第69張票
Thread-2-->正在賣第68張票
Thread-2-->正在賣第67張票
Thread-2-->正在賣第66張票
Thread-2-->正在賣第65張票
Thread-2-->正在賣第64張票
Thread-2-->正在賣第63張票
Thread-2-->正在賣第62張票
Thread-2-->正在賣第61張票
Thread-2-->正在賣第60張票
Thread-2-->正在賣第59張票
Thread-2-->正在賣第58張票
Thread-2-->正在賣第57張票
Thread-2-->正在賣第56張票
Thread-2-->正在賣第55張票
Thread-2-->正在賣第54張票
Thread-2-->正在賣第53張票
Thread-2-->正在賣第52張票
Thread-2-->正在賣第51張票
Thread-2-->正在賣第50張票
Thread-2-->正在賣第49張票
Thread-2-->正在賣第48張票
Thread-2-->正在賣第47張票
Thread-2-->正在賣第46張票
Thread-2-->正在賣第45張票
Thread-2-->正在賣第44張票
Thread-2-->正在賣第43張票
Thread-2-->正在賣第42張票
Thread-2-->正在賣第41張票
Thread-2-->正在賣第40張票
Thread-2-->正在賣第39張票
Thread-2-->正在賣第38張票
Thread-2-->正在賣第37張票
Thread-2-->正在賣第36張票
Thread-2-->正在賣第35張票
Thread-2-->正在賣第34張票
Thread-2-->正在賣第33張票
Thread-2-->正在賣第32張票
Thread-2-->正在賣第31張票
Thread-2-->正在賣第30張票
Thread-2-->正在賣第29張票
Thread-2-->正在賣第28張票
Thread-2-->正在賣第27張票
Thread-2-->正在賣第26張票
Thread-2-->正在賣第25張票
Thread-2-->正在賣第24張票
Thread-2-->正在賣第23張票
Thread-2-->正在賣第22張票
Thread-2-->正在賣第21張票
Thread-2-->正在賣第20張票
Thread-2-->正在賣第19張票
Thread-2-->正在賣第18張票
Thread-2-->正在賣第17張票
Thread-2-->正在賣第16張票
Thread-2-->正在賣第15張票
Thread-2-->正在賣第14張票
Thread-2-->正在賣第13張票
Thread-2-->正在賣第12張票
Thread-2-->正在賣第11張票
Thread-2-->正在賣第10張票
Thread-2-->正在賣第9張票
Thread-2-->正在賣第8張票
Thread-2-->正在賣第7張票
Thread-2-->正在賣第6張票
Thread-2-->正在賣第5張票
Thread-2-->正在賣第4張票
Thread-2-->正在賣第3張票
Thread-2-->正在賣第2張票
Thread-2-->正在賣第1張票

同步方法和靜態同步的方法的不一樣點在於,同步鎖的對象不一樣。

同步方法     的鎖對象就是 實現類對象  new RunnableImpl()
靜態同步的方法  的鎖對象就是 本類的 RunnableImpl.class。

  3)、鎖機制

代碼實現:

/*
    賣票案例出現了線程安全問題
    賣出了不存在的票和重複的票

    解決線程安全問題的三種方案:使用Lock鎖
    java.util.concurrent.locks.Lock接口
    Lock 實現提供了比使用 synchronized 方法和語句可得到的更普遍的鎖定操做。
    Lock接口中的方法:
        void lock()獲取鎖。
        void unlock()  釋放鎖。
    java.util.concurrent.locks.ReentrantLock implements Lock接口


    使用步驟:
        1.在成員位置建立一個ReentrantLock對象
        2.在可能會出現安全問題的代碼前調用Lock接口中的方法lock獲取鎖
        3.在可能會出現安全問題的代碼後調用Lock接口中的方法unlock釋放鎖
 */
public class RunnableImpl implements Runnable{
    //定義一個多個線程共享的票源
    private int ticket = 100;
    //1.在成員位置建立一個ReentrantLock對象
    Lock lock1 = new ReentrantLock();
    //設置線程任務:賣票
    @Override
    public void run() {
        //使用死循環,讓賣票操做重複執行
        while(true){
            lock1.lock(); //2.在可能會出現安全問題的代碼前調用Lock接口中的方法lock獲取鎖
            if(ticket>0){
                try {
                    Thread.sleep(10); //提升安全問題出現的機率,讓程序睡眠
                    //票存在,賣票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock1.unlock();//3.在可能會出現安全問題的代碼後調用Lock接口中的方法unlock釋放鎖
                    //放在finally{} 中 不管程序是否異常,都會把鎖釋放。
                }
            }
        }
    }
}

結果同上面的兩種方式相同。

 5、線程的狀態

線程狀態  致使狀態發生條件
NEW(新建)  線程剛被建立,可是並未啓動。還沒調用start方法。
Runnable(可運行)
線程能夠在java虛擬機中運行的狀態,可能正在運行本身代碼,也可能沒有,這取決於操
做系統處理器。
Blocked(鎖阻塞)
當一個線程試圖獲取一個對象鎖,而該對象鎖被其餘的線程持有,則該線程進入Blocked狀
態;當該線程持有鎖時,該線程將變成Runnable狀態。
Waiting(無限等待)
一個線程在等待另外一個線程執行一個(喚醒)動做時,該線程進入Waiting狀態。進入這個
狀態後是不能自動喚醒的,必須等待另外一個線程調用notify或者notifyAll方法纔可以喚醒。
TimedWaiting(計時等待)
同waiting狀態,有幾個方法有超時參數,調用他們將進入Timed Waiting狀態。這一狀態
將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數的經常使用方法有Thread.sleep 、Object.wait。
Teminated(被終止)
由於run方法正常退出而死亡,或者由於沒有捕獲的異常終止了run方法而死亡。

  一、Timed Waiting 線程狀態圖: 

 

 

  sleep方法的使用仍是很簡單的。咱們須要記住下面幾點:
    1. 進入 TIMED_WAITING 狀態的一種常見情形是調用的 sleep 方法,
      單獨的線程也能夠調用,不必定非要有協做關係。
    2. 爲了讓其餘線程有機會執行,能夠將Thread.sleep()的調用放線程run()以內。
      這樣才能保證該線程執行過程當中會睡眠
    3. sleep與鎖無關,線程睡眠到期自動甦醒,並返回到Runnable(可運行)狀態。
 
    注:sleep()中指定的時間是線程不會運行的最短期。
    所以,sleep()方法不能保證該線程睡眠到期後就開始馬上執行。

  二、Blocked(鎖阻塞)

  上面已經講過了同步機制,那麼這個狀態也就很是好理解了,好比線程A與線程B代碼中使用

  同一把鎖,若是線程A獲取到鎖,線程A進入到Runnable狀態,那麼線程B進入到Blocked鎖阻塞狀態。

  這裏是由Runnable狀態進入Blocked狀態,除此以外Waiting(無限等待)以及

  Time Waiting(計時等待)也會在某種狀況下進入到阻塞狀態

  三、Waiting(無限等待)

    一、等待與喚醒案例:

/*
    等待喚醒案例:線程之間的通訊
        建立一個顧客線程(消費者):告知老闆要的包子的種類和數量,調用wait方法,放棄cpu的執行,進入到WAITING狀態(無限等待)
        建立一個老闆線程(生產者):花了5秒作包子,作好包子以後,調用notify方法,喚醒顧客吃包子

    注意:
        顧客和老闆線程必須使用同步代碼塊包裹起來,保證等待和喚醒只能有一個在執行
        同步使用的鎖對象必須保證惟一
        只有鎖對象才能調用wait和notify方法

    Obejct類中的方法
    void wait()
          在其餘線程調用此對象的 notify() 方法或 notifyAll() 方法前,致使當前線程等待。
    void notify()
          喚醒在此對象監視器上等待的單個線程。
          會繼續執行wait方法以後的代碼
 */
public class MainThread {
    public static void main(String[] args) {
        //建立鎖對象,保證惟一
        Object obj = new Object();
        // 建立一個顧客線程(消費者)
        new Thread(){
            @Override
            public void run() {
                //一直等着買包子
                while(true){
                    //保證等待和喚醒的線程只能有一個執行,須要使用同步技術
                    synchronized (obj){
                        System.out.println("告知老闆要的包子的種類和數量");
                        //調用wait方法,放棄cpu的執行,進入到WAITING狀態(無限等待)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //喚醒以後執行的代碼
                        System.out.println("包子已經作好了,開吃!");
                        System.out.println("---------------------------------------");
                    }
                }
            }
        }.start();

        //建立一個老闆線程(生產者)
        new Thread(){
            @Override
            public void run() {
                //一直作包子
                while (true){
                    //花了5秒作包子
                    try {
                        Thread.sleep(5000);//花5秒鐘作包子
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //保證等待和喚醒的線程只能有一個執行,須要使用同步技術
                    synchronized (obj){
                        System.out.println("老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了");
                        //作好包子以後,調用notify方法,喚醒顧客吃包子
 obj.notify();
                    }
                }
            }
        }.start();
    }
}

結果:

告知老闆要的包子的種類和數量
老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了
包子已經作好了,開吃!
---------------------------------------
告知老闆要的包子的種類和數量
老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了
包子已經作好了,開吃!
---------------------------------------
告知老闆要的包子的種類和數量

  二、等待與喚醒案例2:wait(long m)和notifyAll() 方法

代碼實現:

/*
    進入到TimeWaiting(計時等待)有兩種方式
    1.使用sleep(long m)方法,在毫秒值結束以後,線程睡醒進入到Runnable/Blocked狀態
    2.使用wait(long m)方法,wait方法若是在毫秒值結束以後,尚未被notify喚醒,就會自動醒來,線程睡醒進入到Runnable/Blocked狀態

    喚醒的方法:
         void notify() 喚醒在此對象監視器上等待的單個線程。
         void notifyAll() 喚醒在此對象監視器上等待的全部線程。
 */
public class MainThread {
    public static void main(String[] args) {
        //建立鎖對象,保證惟一
        Object obj = new Object();
        // 建立一個顧客線程(消費者)
        new Thread(){
            @Override
            public void run() {
                //一直等着買包子
                while(true){
                    //保證等待和喚醒的線程只能有一個執行,須要使用同步技術
                    synchronized (obj){
                        System.out.println("顧客1告知老闆要的包子的種類和數量");
                        //調用wait方法,放棄cpu的執行,進入到WAITING狀態(無限等待)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //喚醒以後執行的代碼
                        System.out.println("包子已經作好了,顧客1開吃!");
                        System.out.println("---------------------------------------");
                    }
                }
            }
        }.start();

        // 建立一個顧客線程(消費者)
        new Thread(){
            @Override
            public void run() {
                //一直等着買包子
                while(true){
                    //保證等待和喚醒的線程只能有一個執行,須要使用同步技術
                    synchronized (obj){
                        System.out.println("顧客2告知老闆要的包子的種類和數量");
                        //調用wait方法,放棄cpu的執行,進入到WAITING狀態(無限等待)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //喚醒以後執行的代碼
                        System.out.println("包子已經作好了,顧客2開吃!");
                        System.out.println("---------------------------------------");
                    }
                }
            }
        }.start();

        //建立一個老闆線程(生產者)
        new Thread(){
            @Override
            public void run() {
                //一直作包子
                while (true){
                    //花了5秒作包子
                    try {
                        Thread.sleep(5000);//花5秒鐘作包子
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //保證等待和喚醒的線程只能有一個執行,須要使用同步技術
                    synchronized (obj){
                        System.out.println("老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了");
                        //作好包子以後,調用notify方法,喚醒顧客吃包子
                        //obj.notify();//若是有多個等待線程,隨機喚醒一個
                        obj.notifyAll();//喚醒全部等待的線程
                    }
                }
            }
        }.start();
    }
}

 

結果:

顧客1告知老闆要的包子的種類和數量
顧客2告知老闆要的包子的種類和數量
老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了
包子已經作好了,顧客2開吃!
---------------------------------------
顧客2告知老闆要的包子的種類和數量
包子已經作好了,顧客1開吃!
---------------------------------------
顧客1告知老闆要的包子的種類和數量
老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了
包子已經作好了,顧客1開吃!
---------------------------------------
顧客1告知老闆要的包子的種類和數量
包子已經作好了,顧客2開吃!
---------------------------------------

等待與喚醒機制:又名 線程之間的通信。

  等待喚醒機制就是用於解決線程間通訊的問題的,使用到的3個方法的含義以下:
  1. wait:線程再也不活動,再也不參與調度,進入 wait set 中,所以不會浪費 CPU 資源,也不會去競爭鎖了,
    這時的線程狀態便是 WAITING。它還要等着別的線程執行一個特別的動做,也便是「通知(notify)」在這個對象
    上等待的線程從wait set 中釋放出來,從新進入到調度隊列(ready queue)中
  2. notify:則選取所通知對象的 wait set 中的一個線程釋放;例如,餐館有空位置後,等候就餐最久的顧客最早入座。
  3. notifyAll:則釋放所通知對象的 wait set 上的所有線程。
 
  注: 哪怕只通知了一個等待的線程,被通知線程也不能當即恢復執行,由於它當初 中斷的地方是在同步塊內,而
    此刻 它已經不持有鎖,因此它 須要再次嘗試去獲取鎖(極可能面臨其它線程的競爭),成功後才能在當初調
     用 wait 方法以後的地方恢復執行
 
  總結以下:
  若是能獲取鎖,線程就從 WAITING 狀態變成 RUNNABLE 狀態;
  不然,從 wait set 出來,又進入 entry set,線程就從 WAITING 狀態又變成 BLOCKED 狀態

以下所示:

調用wait和notify方法須要注意的細節
  1. wait方法與notify方法必需要由同一個鎖對象調用。由於:對應的鎖對象能夠經過notify喚醒使用同一個鎖對
    象調用的wait方法後的線程。
  2. wait方法與notify方法是屬於Object類的方法的。由於:鎖對象能夠是任意對象,而任意對象的所屬類都是繼
    承了Object類的。
  3. wait方法與notify方法必需要在同步代碼塊或者是同步函數中使用。由於:必需要經過鎖對象調用這2個方法。 

   三、生產者和消費者問題

  等待喚醒機制其實就是經典的「生產者與消費者」的問題。

  從新整理代碼:

  BaoZi類:

/*
    資源類:包子類
    設置包子的屬性
        皮
        陷
        包子的狀態: 有 true,沒有 false
 */
public class BaoZi {
    private String pier ;
    private String xianer ;
    private boolean flag = false ;//包子資源 是否存在 包子資源狀態

    public String getPier() {
        return pier;
    }

    public void setPier(String pier) {
        this.pier = pier;
    }

    public String getXianer() {
        return xianer;
    }

    public void setXianer(String xianer) {
        this.xianer = xianer;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

  BaoZiPu類:

/*
    生產者(包子鋪)類:是一個線程類,能夠繼承Thread
    設置線程任務(run):生產包子
    對包子的狀態進行判斷
    true:有包子
        包子鋪調用wait方法進入等待狀態
    false:沒有包子
        包子鋪生產包子
        增長一些趣味性:交替生產兩種包子
            有兩種狀態(i%2==0)
        包子鋪生產好了包子
        修改包子的狀態爲true有
        喚醒吃貨線程,讓吃貨線程吃包子

    注意:
        包子鋪線程和包子線程關係-->通訊(互斥)
        必須同時同步技術保證兩個線程只能有一個在執行
        鎖對象必須保證惟一,可使用包子對象做爲鎖對象
        包子鋪類和吃貨的類就須要把包子對象做爲參數傳遞進來
            1.須要在成員位置建立一個包子變量
            2.使用帶參數構造方法,爲這個包子變量賦值
 */
public class BaoZiPu extends Thread {
    //1.須要在成員位置建立一個包子變量
    private BaoZi bz;

    //2.使用帶參數構造方法,爲這個包子變量賦值
    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }

    //設置線程任務(run):生產包子
    @Override
    public void run() {
        //定義一個變量
        int count = 0;
        //讓包子鋪一直生產包子
        while(true){
            //必須同時同步技術保證兩個線程只能有一個在執行
            synchronized (bz){
                //對包子的狀態進行判斷
                if(bz.isFlag()==true){
                    //包子鋪調用wait方法進入等待狀態
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被喚醒以後執行,包子鋪生產包子
                //增長一些趣味性:交替生產兩種包子
                if(count%2==0){
                    //生產 薄皮三鮮餡包子
                    bz.setPier("薄皮");
                    bz.setXianer("三鮮餡");
                }else{
                    //生產 冰皮 牛肉大蔥陷
                    bz.setPier("冰皮");
                    bz.setXianer("牛肉大蔥陷");
                }
                count++;
                System.out.println("包子鋪正在生產:"+bz.getPier()+bz.getXianer()+"包子");
                //生產包子須要3秒鐘
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //包子鋪生產好了包子
                //修改包子的狀態爲true有
                bz.setFlag(true);
                //喚醒吃貨線程,讓吃貨線程吃包子
                bz.notify();
                System.out.println("包子鋪已經生產好了:"+bz.getPier()+bz.getXianer()+"包子,吃貨能夠開始吃了");
            }
        }
    }
}

  ChiHuo類:

/*
    消費者(吃貨)類:是一個線程類,能夠繼承Thread
    設置線程任務(run):吃包子
    對包子的狀態進行判斷
    false:沒有包子
        吃貨調用wait方法進入等待狀態
    true:有包子
        吃貨吃包子
        吃貨吃完包子
        修改包子的狀態爲false沒有
        吃貨喚醒包子鋪線程,生產包子
 */
public class ChiHuo extends Thread{
    //1.須要在成員位置建立一個包子變量
    private BaoZi bz;

    //2.使用帶參數構造方法,爲這個包子變量賦值
    public ChiHuo(BaoZi bz) {
        this.bz = bz;
    }
    //設置線程任務(run):吃包子
    @Override
    public void run() {
        //使用死循環,讓吃貨一直吃包子
        while (true){
            //必須同時同步技術保證兩個線程只能有一個在執行
            synchronized (bz){
                //對包子的狀態進行判斷
                if(bz.isFlag()==false){
                    //吃貨調用wait方法進入等待狀態
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被喚醒以後執行的代碼,吃包子
                System.out.println("吃貨正在吃:"+bz.getPier()+bz.getXianer()+"的包子");
                //吃貨吃完包子
                //修改包子的狀態爲false沒有
                bz.setFlag(false);
                //吃貨喚醒包子鋪線程,生產包子
                bz.notify();
                System.out.println("吃貨已經把:"+bz.getPier()+bz.getXianer()+"的包子吃完了,包子鋪開始生產包子");
                System.out.println("----------------------------------------------------");
            }
        }
    }
}

  測試類:

/*
    測試類:
    包含main方法,程序執行的入口,啓動程序
    建立包子對象;
    建立包子鋪線程,開啓,生產包子;
    建立吃貨線程,開啓,吃包子;
 */
public class Demo {
    public static void main(String[] args) {
        //建立包子對象;
        BaoZi bz =new BaoZi();
        //建立包子鋪線程,開啓,生產包子;
        new BaoZiPu(bz).start();
        //建立吃貨線程,開啓,吃包子;
        new ChiHuo(bz).start();
    }
}

結果:

包子鋪正在生產:薄皮三鮮餡包子
包子鋪已經生產好了:薄皮三鮮餡包子,吃貨能夠開始吃了
吃貨正在吃:薄皮三鮮餡的包子
吃貨已經把:薄皮三鮮餡的包子吃完了,包子鋪開始生產包子
----------------------------------------------------
包子鋪正在生產:冰皮牛肉大蔥陷包子
包子鋪已經生產好了:冰皮牛肉大蔥陷包子,吃貨能夠開始吃了
吃貨正在吃:冰皮牛肉大蔥陷的包子
吃貨已經把:冰皮牛肉大蔥陷的包子吃完了,包子鋪開始生產包子
----------------------------------------------------
包子鋪正在生產:薄皮三鮮餡包子
包子鋪已經生產好了:薄皮三鮮餡包子,吃貨能夠開始吃了
吃貨正在吃:薄皮三鮮餡的包子
吃貨已經把:薄皮三鮮餡的包子吃完了,包子鋪開始生產包子
----------------------------------------------------
包子鋪正在生產:冰皮牛肉大蔥陷包子

等待與喚醒機制:又名 線程之間的通信。

原理說明:

  通信:對包子的狀態進行判斷

  沒有包子-->吃貨線程 喚醒包子鋪線程-->吃貨等待-->包子鋪線程作包子-->作好包子-->修改包子的狀態爲有包子

  有包子-->包子鋪線程喚醒 吃貨線程-->包子鋪線程等待-->吃貨線程吃包子-->吃完包子-->修改包子的狀態爲沒有包子

  。。。

 6、線程池

  爲何使用線程:

 

     當併發的線程數量不少,而且每一個線程都是執行一個時間很短的任務就結束了,

    這樣頻繁建立線程就會大大的下降系統的效率,由於頻繁建立線程和銷燬線程須要時間。

  一、線程的概念

    線程池:其實就是一個容納多個線程的容器,其中線程能夠反覆使用,省去了頻繁建立線程對象的操做,

    無需反覆建立線程池而消耗了過多的資源。

    圖解:

 

 合理利用線程池可以帶來三個好處:

  一、下降資源消耗。減小了建立和銷燬線程的次數,每一個工做線程均可以被重複使用,可執行多個任務。

  二、提升響應速度。當任務到達時,任務能夠不須要的等到線程建立就能當即執行

    三、提升線程的可管理性。能夠根據系統的承受能力,調整線程池中工做線線程的數目,防止由於消耗過多的內

     存,而把服務器累趴下(每一個線程須要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)

  二、線程池的使用

/*
    2.建立一個類,實現Runnable接口,重寫run方法,設置線程任務
 */
public class RunnableImpl implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"建立了一個新的線程執行");
    }
}

  測試類;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
    線程池:JDK1.5以後提供的
    java.util.concurrent.Executors:線程池的工廠類,用來生成線程池
    Executors類中的靜態方法:
        static ExecutorService newFixedThreadPool(int nThreads) 建立一個可重用固定線程數的線程池
        參數:
            int nThreads:建立線程池中包含的線程數量
        返回值:
            ExecutorService接口,返回的是ExecutorService接口的實現類對象,
       咱們可使用ExecutorService接口接收(面向接口編程)   java.util.concurrent.ExecutorService:線程池接口 用來從線程池中獲取線程,調用start方法,執行線程任務 submit(Runnable task) 提交一個 Runnable 任務用於執行 關閉/銷燬線程池的方法 void shutdown() 線程池的使用步驟: 1.使用線程池的工廠類Executors裏邊提供的靜態方法newFixedThreadPool生產一個指定線程數量的線程池 2.建立一個類,實現Runnable接口,重寫run方法,設置線程任務 3.調用ExecutorService中的方法submit,傳遞線程任務(實現類),開啓線程,執行run方法 4.調用ExecutorService中的方法shutdown銷燬線程池(不建議執行)
*/ public class MainThread { public static void main(String[] args) { //1.使用線程池的工廠類Executors裏邊提供的靜態方法newFixedThreadPool生產一個指定線程數量的線程池 ExecutorService es = Executors.newFixedThreadPool(2); //3.調用ExecutorService中的方法submit,傳遞線程任務(實現類),開啓線程,執行run方法 es.submit(new RunnableImpl());//pool-1-thread-1建立了一個新的線程執行 //線程池會一直開啓,使用完了線程,會自動把線程歸還給線程池,線程能夠繼續使用 es.submit(new RunnableImpl());//pool-1-thread-1建立了一個新的線程執行 es.submit(new RunnableImpl());//pool-1-thread-2建立了一個新的線程執行 //4.調用ExecutorService中的方法shutdown銷燬線程池(不建議執行) es.shutdown(); es.submit(new RunnableImpl());//拋異常,線程池都沒有了,就不能獲取線程了 } }

結果:

pool-1-thread-1建立了一個新的線程執行
pool-1-thread-2建立了一個新的線程執行
pool-1-thread-1建立了一個新的線程執行
pool-1-thread-2建立了一個新的線程執行
pool-1-thread-2建立了一個新的線程執行

  由於線程使用完了,會自動把線程歸還給線程池,線程能夠繼續使用,因此我只在線程池中設置了兩個線程,

  卻能夠反覆使用,執行屢次任務。

 

注:多線程執行時爲何調用的是start()方法而不是run()方法?
    若是調用代碼thread.run()就不是異步執行了,而是同步,那麼此線程對象就不會交給「線程規劃器」來進行處理。而是由main主線程來調用run()方法,也就是說必需要等到run()方法中的代碼執行完成後才能夠執行後面的代碼。
  start()用來啓動一個線程,當調用start()方法時,系統纔會開啓一個線程,經過Thead類中start()方法來啓動的線程處於就緒狀態(可運行狀態),此時並無運行,一旦獲得CPU時間片,就自動開始執行run()方法。 

待續。。。。

相關文章
相關標籤/搜索