2018-04-16

多線程基礎(chǔ)

基本的概念:

多線程其實就是進程中一個獨立的控制單元或者說是執(zhí)行路徑,線程控制著進程的執(zhí)行,【重點】一個進程中,至少有一個線程存在。

目錄

【0】線程的狀態(tài)轉(zhuǎn)換圖

【1】【線程的創(chuàng)建】怎樣繼承Thread類,創(chuàng)建多個線程?這個和實現(xiàn)Runnable接口創(chuàng)建的方式有什么區(qū)別?

【2】創(chuàng)建線程為什么要覆寫run方法和為什么在main方法中執(zhí)行線程程序,每一次運行的效果都不一樣?

【3】怎樣獲得當前線程的名稱?

【4】【重點】線程中存在的安全問題是什么,怎樣發(fā)現(xiàn)線程安全?

【5】【重點】實例分析:汽車站售票程序【其中涉及了線程同步】

【6】同步的兩種表現(xiàn)形式是什么?

【7】懶漢式單例模式分析

【8】多線程的死鎖問題

【9】JDK1.5中關(guān)于線程的新特性

【10】多線程之間的通信模式分析

【11】停止線程的方法

【12】join和yield方法總結(jié)

【13】開發(fā)過程中為了提高效率,怎樣單獨封裝?

開始:

【0】線程的狀態(tài)轉(zhuǎn)換圖

線程狀態(tài)圖.PNG

new:新建狀態(tài)

Runnable:就緒狀態(tài)。線程對象創(chuàng)建后,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運行線程池中,變得可運行,等待獲取CPU的使用權(quán)。

Running:運行狀態(tài)。就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。

Blocked:阻塞狀態(tài)。阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán),暫時停止運行。直到線程進入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)。

對于堵塞的情況原因大概有三種:

1、等待堵塞:運行的線程執(zhí)行wait()方法,JVM把線程放入等待狀態(tài)。

2、同步阻塞:運行的線程在獲取對象的同步鎖時,該鎖被別的線程占用。

3、其他阻塞:運行的線程執(zhí)行sleep()或join()方法,或者發(fā)出I/O請求時?;蛘弋攕leep()狀態(tài)超時、join()等待線程終止或者超時、或I/O處理完畢時,線程重新轉(zhuǎn)入就緒狀態(tài)。

Dead:死亡狀態(tài)。線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。

注意:
   1、yield():線程讓步,暫停當前正在運行執(zhí)行的線程對象,并執(zhí)行其他線程。

   2、join():讓一個線程A加入到另一個線程B的尾部。在A執(zhí)行完之前,B不能操作。另外還有超時限制的功能。如join(5000):讓線程等待5秒,如果超時,則停止等待,變?yōu)榭蛇\行狀態(tài)。

【1】繼承Thread類創(chuàng)建線程和實現(xiàn)Runnable接口來創(chuàng)建線程

Thread類是一個線程類,我們在具體定義定義多線程開發(fā)時,有兩種方式創(chuàng)建線程,其中一種就是通過繼承的方式來完成,覆寫Thread類中的run方法:但是這種創(chuàng)建方式有一定的弊端:那就是被創(chuàng)建的子類不能再繼承其他的類;為了解決這種弊端,一般我們都直接去實現(xiàn)Runnable接口去實現(xiàn)多線程的創(chuàng)建,這其實也正是JAVA中解決多態(tài)性的具體方案,實現(xiàn)一個接口之后,也能去繼承其他的父類或者被子類繼承!

原理明白了,好了,寫個簡單的程序演示演示吧:

class Mythread extends Thread//這里就是一個簡單的繼承創(chuàng)建線程的方式
{
    public void run()//覆寫了父類中的run方法,定義了自定義的函數(shù)主體for循環(huán)
    {
        for (int i=0;i<50 ;i++ )
        {
            System.out.println("thread hao!="+i);//僅僅是演示線程執(zhí)行的內(nèi)容
        }
        
    }
}
//這里就是通過main方法來調(diào)用
class ThreadDemo1 
{
    public static void main(String[] args) 
    {
        Mythread mt=new Mythread();//把這個線程對象實例化
        mt.start();//調(diào)用父類的start方法,啟動并執(zhí)行線程
    }
}

class Cus implements Runnable//這里是通過實現(xiàn)Runnable接口的方式創(chuàng)建多線程的
{
    public void run()//同理也要覆寫接口的run方法
    {
        for (int i=0;i<3;i++ )//簡單的函數(shù)主體
        {
            System.out.println("....."+i);//用于演示而已
        }
    }
}

class SynchronizedDemo4 
{
    public static void main(String[] args) 
    {
        Cus c=new Cus();//創(chuàng)建接口的子類對象
        Thread c1=new Thread(c);//定義了兩個線程,把接口的子類對象和線程相關(guān)聯(lián)起來
        Thread c2=new Thread(c);
        c1.start();//啟動兩個線程并且執(zhí)行
        c2.start();
    }
}

【2】為什么要復寫run方法

復寫run方法主要是因為:在Thread類中,run方法是儲存線程要運行的重要代碼,所以在定義的時候,我們必須根據(jù)具體的需求去覆寫run方法,從而自定義要實現(xiàn)的功能主體!

start方法是父類開啟和執(zhí)行線程的方法,直接調(diào)用就行了,但是我們?nèi)绻桓矊憆un方法,直接在子類中調(diào)用的話,是毫無意義的,因為這樣的話,相當于我們只是創(chuàng)建了線程,而沒有運行!

在main方法中,多次運行多線程的結(jié)果都不一定一樣的原因是:我們知道,多線程在執(zhí)行的時候,都是在獲取cpu的一個執(zhí)行權(quán),cpu說想把執(zhí)行權(quán)給誰(線程),誰(線程)就去執(zhí)行相應(yīng)的操作!但是在某一時刻,只能有一個程序在運行,當然多核除外

這也就反應(yīng)除了多線程的一個重要特性:那就是隨機性?。梢岳斫鉃椋赫l搶到誰就執(zhí)行)

【3】獲取當前線程的名稱

很簡單了:用線程類的靜態(tài)方法就可以:

Thread.currentThread().getName():這樣就能夠獲得當前線程的名稱:

線程名稱的基本格式是:Thread-編號,編號都是從0開始的

【4】發(fā)現(xiàn)線程安全

可以模擬出線程在運行中出現(xiàn)的問題:在這里賣票系統(tǒng)中就是最好的分析****

一般通過下列代碼就能找到其安不安全的地方!因為現(xiàn)在只要在這里休眠一下,其他線程自然會獲得cpu的執(zhí)行權(quán)進來,這樣就沒法保證在共享的代碼中,不出現(xiàn)問題:在不同步的情況下,售票窗口可能會售出0號,甚至是-1號票,這樣就要求我們必須要保證共享數(shù)據(jù)的同步性:

對于發(fā)現(xiàn)線程的安全問題:

具體步驟:

分析:哪里出現(xiàn)了線程的安全問題

如何去找原因:

1.明確哪些代碼是多線程運行的代碼:

2.明確哪里是共享的數(shù)據(jù)

3.明確多線程的運行代碼中哪里涉及到了要執(zhí)行共享的數(shù)據(jù)

                    try { //這種簡單的方式只是模擬出現(xiàn)問題而已:                         
                        Thread.sleep(10);  
                    }  
                    catch (Exception e)  
                    {  
                    } 

【5】汽車站售票小程序

模擬賣票窗口,4個窗口同時賣100張共同的票

/*
實現(xiàn)Runnable接口的具體步驟和解決安全性的問題
*/

//1、繼承Thread類的做法:
/*
class Ticket extends Thread
{
    private static int tickets=100;  
    //為了確保票的唯一性,必須用static修飾,否則每個線程窗口會各自賣100張票

    //覆寫父類的run方法,定義線程賣票方式
    public void run()
    {
        while (true)
        {
            if (tickets>0) //當票大于0時,才能出票
            {
                //打印結(jié)果,獲取各線程(窗口)賣票情況
                System.out.println(Thread.currentThread().getName()+".....sale:"+tickets --);
            }
        }
    }
}

class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        Ticket t1=new Ticket();
        Ticket t2=new Ticket();
        Ticket t3=new Ticket();
        Ticket t4=new Ticket();  //創(chuàng)建4個窗口(4個線程)
        
        t1.start();
        t2.start();
        t3.start();
        t4.start(); //啟動并執(zhí)行窗口的賣票

        /*
        部分結(jié)果:
        Thread-2.....sale:76
        Thread-2.....sale:75
        Thread-2.....sale:74
        Thread-3.....sale:80
        Thread-1.....sale:81
        Thread-1.....sale:71
        Thread-1.....sale:70
        Thread-3.....sale:72
        Thread-2.....sale:73
        Thread-0.....sale:78
        Thread-2.....sale:67
        Thread-2.....sale:65
        Thread-3.....sale:68
        Thread-1.....sale:69
        Thread-3.....sale:63
        Thread-2.....sale:64
        
    }
}
*/
//實現(xiàn)Runnable接口
class Ticket implements Runnable
{
    private int tickets=100;
    Object obj=new Object();
    public void run()
    {
        while (true)
        {
            synchronized(obj)
            {
                if (tickets>0)
                {
                    /*這里是模擬cpu讓進來的某一個線程沒有分配到執(zhí)行權(quán),必然會售出不正常的票
                    try
                    {
                        Thread.sleep(10);
                    }
                    catch (Exception e)
                    {
                    }
                    */

                    //為了解決這樣的安全問題,要用到同步代碼塊
                    try
                    {
                        Thread.sleep(10);
                    }
                    catch (InterruptedException e)
                    {
                    }
                    System.out.println(Thread.currentThread().getName()+"....sale:"+tickets --);
                }else
                {
                    break;
                }
            }
        }
    }
}
class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        Thread t3=new Thread(t);
        Thread t4=new Thread(t);

        /*自定義線程名稱的方式:
        Thread t1=new Thread(t,"窗口1");
        Thread t2=new Thread(t,"窗口2");
        Thread t3=new Thread(t,"窗口3");
        Thread t4=new Thread(t,"窗口4");
        
        部分結(jié)果:
        窗口1....sale:44
        窗口1....sale:43
        窗口1....sale:42
        窗口1....sale:41
        窗口1....sale:40
        窗口4....sale:80
        窗口2....sale:81
        窗口2....sale:37
        窗口2....sale:36
        窗口2....sale:35
        窗口2....sale:34
        窗口2....sale:33
        窗口2....sale:32
        窗口2....sale:31
        */

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

【6】同步的兩種表現(xiàn)形式:

一種是代碼塊同步,一種是函數(shù)同步:

/*
實現(xiàn)Runnable接口的具體步驟和解決安全性的問題
*/

//1、繼承Thread類的做法:
/*
class Ticket extends Thread
{
    private static int tickets=100;  
    //為了確保票的唯一性,必須用static修飾,否則每個線程窗口會各自賣100張票

    //覆寫父類的run方法,定義線程賣票方式
    public void run()
    {
        while (true)
        {
            if (tickets>0) //當票大于0時,才能出票
            {
                //打印結(jié)果,獲取各線程(窗口)賣票情況
                System.out.println(Thread.currentThread().getName()+".....sale:"+tickets --);
            }
        }
    }
}

class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        Ticket t1=new Ticket();
        Ticket t2=new Ticket();
        Ticket t3=new Ticket();
        Ticket t4=new Ticket();  //創(chuàng)建4個窗口(4個線程)
        
        t1.start();
        t2.start();
        t3.start();
        t4.start(); //啟動并執(zhí)行窗口的賣票

        /*
        部分結(jié)果:
        Thread-2.....sale:76
        Thread-2.....sale:75
        Thread-2.....sale:74
        Thread-3.....sale:80
        Thread-1.....sale:81
        Thread-1.....sale:71
        Thread-1.....sale:70
        Thread-3.....sale:72
        Thread-2.....sale:73
        Thread-0.....sale:78
        Thread-2.....sale:67
        Thread-2.....sale:65
        Thread-3.....sale:68
        Thread-1.....sale:69
        Thread-3.....sale:63
        Thread-2.....sale:64
        
    }
}
*/
//實現(xiàn)Runnable接口
class Ticket implements Runnable
{
    private int tickets=100;
    Object obj=new Object();
    public void run()
    {
        while (true)
        {
            synchronized(obj)
            {
                if (tickets>0)
                {
                    /*這里是模擬cpu讓進來的某一個線程沒有分配到執(zhí)行權(quán),必然會售出不正常的票
                    try
                    {
                        Thread.sleep(10);
                    }
                    catch (Exception e)
                    {
                    }
                    */

                    //為了解決這樣的安全問題,要用到同步代碼塊
                    try
                    {
                        Thread.sleep(10);
                    }
                    catch (InterruptedException e)
                    {
                    }
                    System.out.println(Thread.currentThread().getName()+"....sale:"+tickets --);
                }else
                {
                    break;
                }
            }
        }
    }
}
class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        Ticket t=new Ticket();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        Thread t3=new Thread(t);
        Thread t4=new Thread(t);

        /*自定義線程名稱的方式:
        Thread t1=new Thread(t,"窗口1");
        Thread t2=new Thread(t,"窗口2");
        Thread t3=new Thread(t,"窗口3");
        Thread t4=new Thread(t,"窗口4");
        
        部分結(jié)果:
        窗口1....sale:44
        窗口1....sale:43
        窗口1....sale:42
        窗口1....sale:41
        窗口1....sale:40
        窗口4....sale:80
        窗口2....sale:81
        窗口2....sale:37
        窗口2....sale:36
        窗口2....sale:35
        窗口2....sale:34
        窗口2....sale:33
        窗口2....sale:32
        窗口2....sale:31
        */

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

【7】懶漢式單例模式具體分析

/*
寫兩種方式的單例模式:注意其區(qū)別
*/

/*
    餓漢式的單例模式

class Single
{
    private static final Single s=new Single();
    private Single(){}
    public static Single getInstance()
    {
        return s;
    }

}

*/

/*
    懶漢式的單例模式(延遲加載)
*/
class Single 
{
    private static Single s=null;
    private Single(){}
    //1.為了解決這種安全問題,可以同步函數(shù)的方法解決,但是每個線程進來都必須判斷鎖,效率比較低
    //private static synchronized Single getInstance()

    //2.為了提高效率,可以用同步代碼塊的方法解決
    public static Single getInstance()
    {
        if (s==null) //只要有一個線程執(zhí)行成功,那么以后進來的線程就不再判斷鎖了,
        //因為s已經(jīng)不為null了,直接不能創(chuàng)建單例的對象了,直接用就行了
        {
            synchronized(Single.class)
            {
                if (s==null)
                {
                    //--0線程
                    //--1線程
                    //當0線程醒了,會創(chuàng)建一個對象
                    //當1線程醒了,也會創(chuàng)建一個對象
                    //……這就是出現(xiàn)了安全問題,不是單例模式了
                    s=new Single();
                    System.out.println(Thread.currentThread().getName()+"訪問成功!");
                }
            }
        }   
        return s;
    }
}

class SingleDemo6 
{
    public static void main(String[] args) 
    {
        Single.getInstance();//在main方法中通過單例類對外提供的getInstance方法訪問
        //結(jié)果:main訪問成功!
    }
}

【8】多線程的死鎖問題

什么是死鎖:兩個線程彼此之間搶鎖的情況,互不相讓,就產(chǎn)生死鎖

代碼分析:

/*
    自己寫一個死鎖程序
    用鎖的嵌套方式來模擬死鎖
    模擬了兩個線程彼此之間搶鎖的情況,互不相讓,就產(chǎn)生死鎖
*/

class TestDeaDLock implements Runnable
{
    private boolean flag;//設(shè)置flag是為了讓線程進入不一樣的鎖中去執(zhí)行

    TestDeaDLock(boolean flag)//在Runnable子類初始化時就設(shè)置flag
    {
        this.flag=flag;
    }
    public void run()
    {
        if (flag)
        {
            synchronized(MyLock.m1)
            {
                System.out.println("true:----lock! m1 ");
                //演示:拿著鎖1的線程想進入具備鎖2的內(nèi)容中去,必然要拿到鎖2才行
                //此時持有鎖2的線程可能會把鎖2給持著鎖1的線程,但是也可能互不想讓,導致死鎖的產(chǎn)生
                synchronized(MyLock.m2)
                {
                    System.out.println("true:----lock m2 !");

                }
            }
            
        }else
        {
            synchronized(MyLock.m2)
            {
                System.out.println("false:----lock m2 !");
                //演示:拿著鎖2的線程想進入具備鎖1的內(nèi)容中去,必然要拿到鎖1才行
                synchronized(MyLock.m1)
                {
                    System.out.println("false:----lock m1 !");

                }
            }
        }
    }
}

class MyLock //定義自己的鎖類,其中有兩種鎖
{
    static MyLock m1=new MyLock(); //設(shè)置鎖1
    static MyLock m2=new MyLock();//設(shè)置鎖2
}

class DeadLockDemo7 
{
    public static void main(String[] args) 
    {
        Thread t1=new Thread(new TestDeaDLock(true)); //一個設(shè)置為true
        Thread t2=new Thread(new TestDeaDLock(false));//一個設(shè)置為false,主要是為了能夠進入不一樣的鎖中執(zhí)行
        
        t1.start();
        t2.start(); 
            /*
            演示結(jié)果可能會出現(xiàn):
            true:----lock! m1
            false:----lock m2 !
            */

    }
}

【9】JDK1.5中線程的新特

我們都知道,在線程中有一個很重要的機制,那就是等待喚醒機制:例如代碼分析:

/*
線程的等待喚醒機制
代碼的優(yōu)化
【優(yōu)化的重點】
1.資源中:包含基本的屬性,存入,取出函數(shù)
2.存入類:直接調(diào)用資源的存入函數(shù)
3.取出類:直接調(diào)用資源的取出函數(shù)

這樣看起來非常符合邏輯,層次清楚,便于代碼的維護和修改等
*/
//定義了一個共同的資源
class Res
{
    private String name;
    private String sex;
    boolean flag=false;
    public synchronized void set(String name,String sex)
    {
        if (flag)
            try{wait();}catch(Exception e){}
        this.name=name;
        this.sex=sex;
        this.flag=true;
        this.notify();
    }

    public synchronized void out()
    {
        if (!flag)
            try{wait();}catch(Exception e){}
        System.out.println("name="+name+" ....... sex="+sex);
        this.flag=false;
        this.notify();
    }
}
//定義存入資源類
class Input implements Runnable
{
    Res r;
    //存入類在初始化時,就有資源
    Input(Res r)
    {
        this.r=r;
    }
    //覆寫run方法
    public void run()
    {   
        int id=0;
        
        while (true)
        {
            //加鎖為了保證線程的同步,只能允許一個線程在其中操作直到完成位置
            if (id==0)
                r.set("crystal","man");
            else
                r.set("楊女士","女");
            id=(id+1)%2;
        }
    }
}
//定義取出資源類
class Output implements Runnable
{
    Res r;
    Output(Res r)
    {
        this.r=r;
    }
    public void run()
    {
        while (true)
        {
            r.out();
        }
    }
}
/*
    上述的存入資源中和取出資源中:共享的代碼就是存入操作和取出操作,一定要保證通過進行,
    否則,當一個線程進入時,存完一個之后,再進入存第二時(存完名稱時,掛了),這是取出資源的線程
    在運行,這時候就會把出現(xiàn):r.name="楊女士"和r.sex="man";的情況
*/
class InputOutputDemo3
{
    public static void main(String[] args) 
    {
        /*
        Res r=new Res(); //資源實例化

        Input in=new Input(r); //把資源和Runnable子類相關(guān)聯(lián)
        Output out=new Output(r);

        Thread t1=new Thread(in);//定義了兩個不同線程,一個存入,一個取出
        Thread t2=new Thread(out);
        
        t1.start(); //開啟并執(zhí)行線程
        t2.start();
        */

        Res r=new Res();
        new Thread(new Input(r)).start();
        new Thread(new Output(r)).start();

        /*result:
            …………
            …………
            name=crystal ....... sex=man
            name=楊女士 ....... sex=女
            name=crystal ....... sex=man
            name=楊女士 ....... sex=女
            name=crystal ....... sex=man
            name=楊女士 ....... sex=女
            name=crystal ....... sex=man
            name=楊女士 ....... sex=女
            name=crystal ....... sex=man
            name=楊女士 ....... sex=女
            name=crystal ....... sex=man
            name=楊女士 ....... sex=女
            name=crystal ....... sex=man
            name=楊女士 ....... sex=女
            name=crystal ....... sex=man
            name=楊女士 ....... sex=女
            name=crystal ....... sex=man
            name=楊女士 ....... sex=女
            name=crystal ....... sex=man
            …………
            …………
        */
    }
}

那么在這里面用到了同步鎖,在JDK1.5中,新特性出現(xiàn)了:

將同步synchronized替換成為了Lock操作

將Object中的wait,notify,notifyAll,替換成了Condition對象

該對象可以Lock鎖,進行獲取

在該例子中,實現(xiàn)了本方只喚醒對方的操作

用這種新特性:程序會進行得非常完美,完全符合邏輯!不浪費資源

import java.util.concurrent.locks.*;

class Resource
{
    private String name;
    private int id=1;
    boolean flag=false;
    private Lock lock=new ReentrantLock();
    private Condition condition_pro=lock.newCondition();
    private Condition condition_con=lock.newCondition();

    //定義生產(chǎn)者的同步生產(chǎn)函數(shù)
    public void set(String name)
    {
        lock.lock(); //進來就獲得鎖進入
        try
        {
            while (this.flag)
                //condition.await(); //首次進來因為為false,所以直接執(zhí)行下面的語句
                condition_pro.await();//讓自身的線程等待
            this.name=name+"....."+id++;
            System.out.println(Thread.currentThread().getName()+" Producer:----"+this.name);
            this.flag=true;
            //condition.signalAll();//喚醒線程池中所有的線程,為的是讓不要出現(xiàn)全部等待的狀況
            condition_con.signal();//不用喚醒所有了,只需要喚醒線程池中的對方就行,所以不用All了
        }
        catch (Exception e)
        {
        }
        finally
        {
            lock.unlock(); //最終釋放鎖
        }   
    }
    
    //定義消費者的同步消費函數(shù)
    public void out()
    {
        lock.lock();
        try
        {   
            while(!(this.flag))
                //condition.await();
            //同理:
            condition_con.await(); //本方等待
            System.out.println(Thread.currentThread().getName()+" Consumer:"+name);
            this.flag=false;
            //condition.signalAll();
            condition_pro.signal(); //喚醒對方
        }
        catch (Exception e)
        {
        }
        finally
        {
            lock.unlock();
        }
        
    }
}

//定義生產(chǎn)者具備調(diào)用生產(chǎn)功能
class Producer implements Runnable
{   
    private Resource res;
    Producer(Resource res)
    {
        this.res=res;
    }
    public void run()
    {
        while (true)
        {
            res.set("汽車");
        }
        
    }
}
//定義消費者具備消費功能
class Consumer implements Runnable
{   
    private Resource res;
    Consumer(Resource res)
    {
        this.res=res;
    }
    public void run()
    {
        while (true)
        {
            res.out();
        }
    }
}

class ProducerConsumerDemo6
{
    public static void main(String[] args) 
    {
        //把商品資源實例化
        Resource res=new Resource();

        //定義了兩個生產(chǎn)者路線(線程)和兩個消費者路線(線程)
        //并且同時啟動這些線程和執(zhí)行
        new Thread(new Producer(res)).start();
        new Thread(new Producer(res)).start();
        new Thread(new Consumer(res)).start();
        new Thread(new Consumer(res)).start();

        /*
        結(jié)果:
        ………………
        Thread-1 Producer:----汽車.....3522
        Thread-2 Consumer:汽車.....3522
        Thread-0 Producer:----汽車.....3523
        Thread-3 Consumer:汽車.....3523
        Thread-1 Producer:----汽車.....3524
        Thread-2 Consumer:汽車.....3524
        Thread-0 Producer:----汽車.....3525
        Thread-3 Consumer:汽車.....3525
        Thread-1 Producer:----汽車.....3526
        Thread-2 Consumer:汽車.....3526
        Thread-0 Producer:----汽車.....3527
        Thread-3 Consumer:汽車.....3527
        Thread-1 Producer:----汽車.....3528
        Thread-2 Consumer:汽車.....3528
        Thread-0 Producer:----汽車.....3529
        Thread-3 Consumer:汽車.....3529
        Thread-1 Producer:----汽車.....3530
        ………………
        */
    }
}

【10】多線程之間的通信模式分析

多線程之間的通信就是:多個線程對象去訪問共享的數(shù)據(jù)空間,但是每個線程具體的功能是不一樣的,例如:有一個資源,一個線程是向其中存,另一個線程是往里面取出……

/*
線程之間的通信:
*/
//定義了一個共同的資源
class Res
{
    String name;
    String sex;
}
//定義存入資源類
class Input implements Runnable
{
    Res r;
    //存入類在初始化時,就有資源
    Input(Res r)
    {
        this.r=r;
    }
    //覆寫run方法
    public void run()
    {   
        int id=0;
        while (true)
        {
            //加鎖為了保證線程的同步,只能允許一個線程在其中操作直到完成位置
            synchronized(r)
            {
                if (id==0)
                {
                    r.name="crystal";
                    r.sex="man";
                }else
                {
                    r.name="楊女士";
                    r.sex="女";
                }
                id=(id+1)%2;
            }   
        }
    }
}
//定義取出資源類
class Output implements Runnable
{
    Res r;
    Output(Res r)
    {
        this.r=r;
    }
    public void run()
    {
        while (true)
        {
            //加鎖為了保證線程的同步
            //取出資源也是一樣的,必須保證同步,只能有一個線程操作
            synchronized(r)
            {
                System.out.println(r.name+"....."+r.sex);
            }
        }
    }
}
/*
    上述的存入資源中和取出資源中:共享的代碼就是存入操作和取出操作,一定要保證通過進行,
    否則,當一個線程進入時,存完一個之后,再進入存第二時(存完名稱時,掛了),這是取出資源的線程
    在運行,這時候就會把出現(xiàn):r.name="楊女士"和r.sex="man";的情況
*/
class InputOutputDemo1
{
    public static void main(String[] args) 
    {
        Res r=new Res(); //資源實例化

        Input in=new Input(r); //把資源和Runnable子類相關(guān)聯(lián)
        Output out=new Output(r);

        Thread t1=new Thread(in);//定義了兩個不同線程,一個存入,一個取出
        Thread t2=new Thread(out);
        
        t1.start(); //開啟并執(zhí)行線程
        t2.start();
        /*result:
        ………………
        ………………
        楊女士.....女
        楊女士.....女
        楊女士.....女
        crystal.....man
        crystal.....man
        ………………
        ………………
        */
    }
}

【11】停止線程的方法總結(jié)

停止線程的方法:

由于stop方法已經(jīng)過時了

那么怎樣才能停止線程呢?

思想只有一種,那就是讓run方法結(jié)束

一般開啟多線程的運行,運行代碼通常是循環(huán)結(jié)構(gòu)

所以只要控制好循環(huán),就能讓run方法結(jié)束,也就是讓線程結(jié)束

讓線程停止的3中方法:

1.設(shè)置標記:主線程結(jié)束之前改變標記讓run方法結(jié)束

2.使用interrupt():當線程處于wait(必須在同步中進行)或者sleep中斷時(在凍結(jié)的區(qū)域中),用此方法清除凍結(jié),使線程回到運行中,這時會拋出異常,就在異常中設(shè)置標記,結(jié)束run方法,讓線程不再進入去繼續(xù)等待

3.使用守護線程的方法,setDaemon(true):在【線程啟動之前】就設(shè)定為守護線程,主要是為了當主線程結(jié)束時,后臺會自動結(jié)束被守護的線程

【其實我們看到的都是前臺的線程,后臺也有線程在運行,可以理解為后臺依賴前臺的關(guān)系,當前臺結(jié)束了,后臺線程也就over了,這就是守護線程的特點】

class StopThread implements Runnable
{   private boolean flag=true;
    
    //在同步中,當線程進入凍結(jié)狀況時,就不會讀到標記了,線程就不會結(jié)束
    public synchronized void run()
    {
        while (flag)
        {
            try
            {
                wait(); //兩個線程依次進來都是在這里等待了……沒法被喚醒
                //所以該線程沒有被停止掉,并且主線程中改變標記的方法,在這里,線程也無法讀到了

                //在這個時候就要用到線程中的interrupt方法,主要是為了讓線程從凍結(jié)狀態(tài)回到運行狀態(tài)上
                //但是在運行之后,依然會進入等待狀態(tài)!

                //當回到運行狀態(tài)的時候,就需要在拋出的異常中去處理標記,改變標記值就OK了,當看到標記為false的
                //時候兩個線程就無法進入該方法區(qū)了,這樣線程就結(jié)束了
            }
            catch (InterruptedException e)
            {
                System.out.println(Thread.currentThread().getName()+".....Exception");
                flag=false;
            }
            System.out.println(Thread.currentThread().getName()+" run .....");
        }
    }

    public void setFlag()
    {
        flag=false;
    }
}
class StopDemo7 
{
    public static void main(String[] args) 
    {
        StopThread st=new StopThread();
        Thread t1=new Thread(st);
        Thread t2=new Thread(st);

        //t1.setDaemon(true);//分別把t1,t2線程都設(shè)置為守護線程后再啟動
        //t2.setDaemon(true);

        t1.start();
        t2.start();
        int i=0;
        while (true)
        {
            if (i++ == 50)
            {
                st.setFlag(); //主線程中改變了標記,讓其run方法主體結(jié)束,線程必然也結(jié)束了

                //t1.interrupt(); 
                //t2.interrupt();
                //在主線程結(jié)束之前把處于等待的線程用interrupt()方法清除掉凍結(jié)的線程,讓其回到運行狀態(tài)
                
                break;
            }
            System.out.println(Thread.currentThread().getName()+"  ....."+i);
        }
        /*結(jié)果,這里是通過改變標記的方法,結(jié)束run方法體,讓線程無法進入,直到主線程結(jié)束
        ,其他兩個線程也結(jié)束了
        …………
        main  .....41
        main  .....42
        Thread-0 run .....
        main  .....43
        main  .....44
        main  .....45
        main  .....46
        main  .....47
        main  .....48
        main  .....49
        main  .....50
        Thread-1 run .....
        Thread-0 run .....
        */

        /*
        運用了中斷線程,清除了凍結(jié)的方法之后:兩個線程也結(jié)束了,這是在同步中的做法
        main  .....38
        main  .....39
        main  .....40
        main  .....41
        main  .....42
        main  .....43
        main  .....44
        main  .....45
        main  .....46
        main  .....47
        main  .....48
        main  .....49
        main  .....50
        Thread-0.....Exception
        Thread-0 run .....
        Thread-1.....Exception
        Thread-1 run .....
        */

        /*
        設(shè)置為守護線程后運行的結(jié)果:不論是不是同步,主線程結(jié)束,后臺線程就被Over了
        ………………
        main  .....35
        main  .....36
        main  .....37
        main  .....38
        main  .....39
        main  .....40
        main  .....41
        main  .....42
        main  .....43
        main  .....44
        main  .....45
        main  .....46
        main  .....47
        main  .....48
        main  .....49
        main  .....50

        */
    }
}

【12】join()和yield()方法的總結(jié)

《1》首先join方法:

join():方法的作用

1.當線程A執(zhí)行到了線程B的B.join()方法時,A就會釋放執(zhí)行權(quán)給B,自己處于等待的凍結(jié)狀態(tài);

2.當線程B都執(zhí)行完之后,線程A才能從凍結(jié)狀態(tài)回到運行狀態(tài)去執(zhí)行;

3.所以join可以用來臨時加入線程執(zhí)行內(nèi)容!

了解:

線程A把執(zhí)行權(quán)釋放了,讓線程B去執(zhí)行,自己凍結(jié),這時線程B如果被等待了,線程A也沒法回到運行狀態(tài)

那么,這時就要用到interrupt()方法,去中斷清除A的凍結(jié),從而回到運行狀態(tài),當然也可以中斷線程A

只是會受到傷害(拋出異常),在異常中處理就行了實際開發(fā)中:一般都是使用匿名內(nèi)部類來完成的

class TestJoin implements Runnable
{
    public void run()
    {
        for (int i=0;i<80 ;i++ )
        {
            System.out.println(Thread.currentThread().getName()+".....run....."+i);
        }
        
    }
}

class JoinDemo8 
{
    public static void main(String[] args) 
    {
        TestJoin tj=new TestJoin();
        Thread t1=new Thread(tj);
        Thread t2=new Thread(tj);

        t1.start();

        //try{t1.join();}catch(Exception e){} 
        //執(zhí)行的結(jié)果是:線程0全部把run函數(shù)執(zhí)行完之后,主線程mian和線程1才交替執(zhí)行
        //所以:join的功能是能夠臨時獲得主線程的執(zhí)行權(quán),此程序中,主線程main把執(zhí)行權(quán)釋放給了t1,
        //自己處于了凍結(jié)狀態(tài),當t1線程結(jié)束之后,主線程才回到運行狀態(tài)和t2線程繼續(xù)交替執(zhí)行
        
        t2.start();
        try{t1.join();}catch(Exception e){} 
        //如果t1.join()處于這里的話:主線程仍然是把自己的執(zhí)行權(quán)釋放給了t1,自己處于凍結(jié)狀態(tài),
        //但是t2也是存活的線程,這時候cpu會自動發(fā)放執(zhí)行權(quán)給t1或者t2去交替執(zhí)行!主線程就悲催了,只有等到
        //t1執(zhí)行完之后,才能拿到自己的執(zhí)行權(quán),從凍結(jié)狀態(tài)回到運行狀態(tài)

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

《2》yield()方法:

暫停當前正在執(zhí)行的線程對象,并執(zhí)行其他線程。

當線程A進入時,暫停當前正在執(zhí)行的內(nèi)容,釋放執(zhí)行權(quán),讓其他的線程進來執(zhí)行,當其他的進來執(zhí)行

之后,又重新獲得執(zhí)行權(quán),繼續(xù)執(zhí)行,這樣一來,等同于被共享的線程執(zhí)行內(nèi)容是“交替執(zhí)行的‘

class TestSetPriority implements Runnable
{
    int i=100;
    public void run()
    {
        for (int i=0; i<20 ; i++)
        {
            System.out.println(Thread.currentThread().toString()+"....run...."+i);
            Thread.yield();
            /*
            Thread[Thread-0,5,main]....run....0
            Thread[Thread-1,5,main]....run....0
            Thread[Thread-0,5,main]....run....1
            Thread[Thread-1,5,main]....run....1
            Thread[Thread-0,5,main]....run....2
            Thread[Thread-1,5,main]....run....2
            Thread[Thread-0,5,main]....run....3
            Thread[Thread-1,5,main]....run....3
            Thread[Thread-0,5,main]....run....4
            Thread[Thread-1,5,main]....run....4
            Thread[Thread-0,5,main]....run....5
            Thread[Thread-1,5,main]....run....5
            */
        }
    }
}

class SetPriorityDemo9 
{
    public static void main(String[] args) 
    {
        TestSetPriority sd=new TestSetPriority();
        Thread t1=new Thread(sd);
        Thread t2=new Thread(sd);
        t1.start();
        t2.start();

        /*
        for (int i=0; i<20; i++)
        {
            System.out.println(Thread.currentThread().toString()+"....mians....");
            /*
            Thread[main,5,main]....mian....
            Thread[main,5,main]....mian....
            Thread[main,5,main]....mian....
            
        }
        */
        /*結(jié)果://這里是toString()方法,該線程類覆寫了Object的這個方法
        在其中封裝了進了線程名稱,線程優(yōu)先級,線程組
        …………………………
        Thread[Thread-0,5,main]....run.... 
        Thread[Thread-1,5,main]....run....
        Thread[Thread-0,5,main]....run....
        Thread[Thread-1,5,main]....run....
        Thread[Thread-0,5,main]....run....
        Thread[Thread-1,5,main]....run....
        Thread[Thread-0,5,main]....run....
        Thread[Thread-1,5,main]....run....
        Thread[Thread-0,5,main]....run....
        Thread[Thread-1,5,main]....run....
        Thread[Thread-0,5,main]....run....
        Thread[Thread-1,5,main]....run....
        ………………………………
        */

    }
}

【13】開發(fā)過程中為了提高效率,怎樣單獨封裝?

實際開發(fā)中:一般都是使用匿名內(nèi)部類來完成的 用法:在獨立運算中:相互不相干擾的時候,可以單獨封裝一下,提高了執(zhí)行效率 下面就是三個線程同時執(zhí)行,很高效!

class StandardThreadDemo10 
{
    public static void main(String[] args) 
    {

        //相當于繼承的方法
        new Thread(){
            public void run()
            {
                for (int i=0; i<5 ;i++ )
                {
                    System.out.println(Thread.currentThread().getName()+"...."+i);
                }
            }
        }.start();


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

        //相當于實現(xiàn)接口的方法
        Runnable r=new Runnable()
        {
            public void run()
            {
                for (int i=0; i<10 ;i++ )
                {
                    System.out.println(Thread.currentThread().getName()+"...."+i);
                }
            }
        };
        new Thread(r).start();

        /*
        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-1....0
        Thread-1....1
        Thread-1....2
        Thread-1....3
        Thread-1....4
        Thread-1....5
        Thread-1....6
        Thread-1....7
        Thread-1....8
        Thread-1....9
        */
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java多線程學習 [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,106評論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,593評論 1 15
  • 單任務(wù) 單任務(wù)的特點是排隊執(zhí)行,也就是同步,就像再cmd輸入一條命令后,必須等待這條命令執(zhí)行完才可以執(zhí)行下一條命令...
    Steven1997閱讀 1,349評論 0 6
  • 今天在適配iPhone X的時候,發(fā)現(xiàn)iPhone X的屏幕上下沒有鋪滿。新建的項目,以及寫的Demo都是鋪滿...
    manger閱讀 2,062評論 2 4
  • 今年的桃花,看了三處: 三月的重慶,四月初的杭州,四月末的天津。在天津還看到了重慶不易見的紫藤和紫丁香。最后一張照...
    清音澗閱讀 362評論 0 0

友情鏈接更多精彩內(nèi)容