<form id="dlljd"></form>
        <address id="dlljd"><address id="dlljd"><listing id="dlljd"></listing></address></address>

        <em id="dlljd"><form id="dlljd"></form></em>

          <address id="dlljd"></address>
            <noframes id="dlljd">

              聯系我們 - 廣告服務 - 聯系電話:
              您的當前位置: > 關注 > > 正文

              速讀:程序、進程和線程——多線程的創建方法

              來源:CSDN 時間:2023-03-07 11:40:09

              目錄


              【資料圖】

              程序、進程和線程的概念

              多線程的優點

              Thread類關于多線程的創建

              Thread類的相關方法

              線程的調度

              線程的五種狀態

              線程的同步

              總結同步方法

              衍生內容————單例設計模式

              死鎖問題

              鎖的概念

              sleep()和wait()的異同

              首先要明確幾個概念

              程序、進程和線程的概念

              程序:完成特定任務,用某種特殊的語言編寫的一組指令的集合

              進程:是執行路徑,一個進程同一時間并行或者正在運行的程序

              線程:是執行路徑,一個進程同一時間并行或者執行多個進程,就是多線程

              注:進程中也有可能有多個線程

              CPU也分為多核CPU和單核CPU

              單核CPU:實際上進行的是某種意義上的假CPU,一個CPU同時做好多事,如果一個沒有準備好,就先將該事件掛起,去進行別的,可以用一張圖來表示

              多核CPU(取決與主頻來利用哪個):多核CPU就相當于多個單核CPU工作

              同時也要解釋兩個詞的含義

              并行:多個CPU任務一起進行

              并發:一個CPU做多個任務

              注:并發只是看上去“同時”,但是實際上只是在CPU上進行高速的切換任務,以至于僅僅是看上去是同時,并行才是真正意義上的同時

              多線程的優點

              1、提高應用程序的響應

              2、提高CPU的利用率

              3、改善程序結構,每個線程獨立運行,互不干擾,便于修改

              提到多線程,就不得不提一個特殊的類

              Thread類關于多線程的創建

              方法一:

              1、創建一個繼承于Thread類的子類

              2、重寫Thread類中的run()

              3、創建Thread類子類的對象(要在主線程上創建)、

              4、通過對象去調用start()

              想要創建一個多線程的代碼如下

              //主函數中的體現為//1、創建了繼承Thread的子類//在繼承Thread中的表現為public class ExtendsThread extends Thread {    @Override    //2、此處為標準的對于run()函數重寫    //對run()函數的重寫就相當于對于這一條線程中你想做的所有任務    public void run() {        super.run();        for(int i=0;i<=20;i++)        {            System.out.println(i);        }    }}public class ThreadTest {    public static void main(String[]args) {        //3、創建了繼承Thread子類的對象        Thread et=new ExtendsThread();        //4、通過對象調用了start()        et.start();        //調用start()之后就開啟多線程    }}

              此處需要注意的是

              1、run方法的重寫:將這個線程要執行的所有操作全部都聲明在run方法中

              2、et.run()也能在主函數中直接調用,也能完整的執行在run方法中的指令,但是不能體現多線程,就僅僅是將指令完成,et.run()就僅僅只是調用方法看

              3、不能夠讓已經start()的線程再去重啟線程

              4、可以創建多個對于ExtendsThread的對象,此時這個對象可以再次開始start(),相當于多開了一個線程,只不過執行的是相同內容

              5、匿名子類與匿名對象同樣適用

              public class ThreadTest {    public static void main(String[]args) {        Thread et=new ExtendsThread();        //此處為體現多線程,同時開啟兩個線程        et.start();        //以下即為匿名子類        //直接開啟多線程        new Thread(){            public void run()            {                super.run();                for(int i=0;i<=10;i++)                {                    System.out.println(i+"#"+i);                }            }        }.start();    }}public class ExtendsThread extends Thread {    @Override    public void run() {        super.run();        for(int i=0;i<=10;i++)        {            System.out.println(i+"*"+i);        }    }}

              第一次的執行結果

              方法二:

              1、創建一個實現了Runnable接口的類

              2、實現Runnable接口中的抽象方法

              3、創建實現類對象

              4、將此對象作為參數傳至Thread類的構造器,創造Thread類的對象

              5、利用Thread()類的對象調用start()

              public class RunnalbeThread implements Runnable//1、創建一個實現Runnable的類{    @Override//2、類中重寫Runnable的方法,也就是run方法    public void run() {        for(int i=1;i<=10;i++)        {            System.out.println(Thread.currentThread().getName()+":"+i);        }    }}public class ThreadTest {    public static void main(String[] args) {        Thread rt=new Thread(new RunnalbeThread());        //3、創建一個對應類的對象        //4、將這個對象傳入到Thread的構造器        rt.start();        //5、用這個對應的Thread對象來繼續調用start()        rt.setName("線程3");        for(int i=1;i<=10;i++)        {            System.out.println(Thread.currentThread().getName()+":"+i+"-"+Thread.currentThread().isAlive());        }    }}

              在這個地方,如果沒有創建匿名對象(對于實現Runnable的實現類),一個實現類的對象,可以多次傳入到Thread的構造器里面,創造更多的線程

              兩種方法的比較

              繼承法(方法一)由于Java的單繼承性,導致如果需要繼承Thread類的類由原本的一套體系,可能會影響該代碼的實現,由此看來,實現接口的方式是更加活泛的,更自由。

              實操中優先選擇Runnable接口的方式

              1、實現的方式沒有單繼承性的限制

              2、實現的方式更適合多個線程共享數據的情況

              注:Thread類也實現了Runnable接口

              Thread類的相關方法

              1、String getName();

              返回線程名稱

              2、void setName(String name);

              設置線程名稱

              public static void main(String[] args) {        Thread et = new ExtendsThread();        et.setName("線程--1");        System.out.printf(et.getName());}

              運行結果

              此處需要注意的是,主線程也是可以命名的,如以下代碼

              public class ThreadTest {    public static void main(String[] args) {        Thread et = new ExtendsThread();        Thread.currentThread().setName("主線程");        System.out.printf(Thread.currentThread().getName());    }}

              運行結果如下

              3、currentThread()方法

              靜態方法,返回當前執行此代碼的線程(對象)

              4、yield()方法

              釋放當前CPU的執行權

              也存在當我們釋放完執行權之后,CPU再次將執行權分配給目前線程的情況

              5、join()方法

              相當于在原本的線程1上,讓另一個線程2截斷,知道這個線程2執行結束,否則不再進行線程1(在線程1之中調用線程2的join方法)

              代碼測試如下

              public class ExtendsThread extends Thread{    @Override    public void run() {        super.run();        for(int i=0;i<=10;i++)        {            System.out.println(Thread.currentThread().getName()+":"+i+"*"+i);        }    }}public class ExtendsThread2 extends Thread{    public void run() {        super.run();        for(int i=0;i<=10;i++)        {            System.out.println(Thread.currentThread().getName()+":"+i+"#"+i);        }    }}public class ThreadTest {    public static void main(String[] args) {        Thread et = new ExtendsThread();        Thread et2=new ExtendsThread2();        et.start();        et2.start();        et.setName("線程1");        et2.setName("線程2");        Thread.currentThread().setName("主線程");        for(int i=0;i<=20;i++)        {            System.out.println(Thread.currentThread().getName()+":"+i);            if(i%5==0)            {                try {                    et2.join();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}

              測試結果如下

              當主線程的i跑到5的時候,此時調用了et.join()和et2.join()此時的主線程已經被掛起了,直到線程1和線程2運行完之后,才會繼續主線程的進行。

              6、stop()

              強制結束線程,可以提前結束線程的生命周期。(不推薦使用stop()結束進程)

              public class ExtendsThread extends Thread{    @Override    public void run() {        super.run();        for(int i=0;i<=10;i++)        {            System.out.println(Thread.currentThread().getName()+":"+i+"*"+i);            if(i==5)            {                Thread.currentThread().stop();                //此處用stop強制停止了                //當i=5的時候強制停止線程            }        }    }}public class ThreadTest {    public static void main(String[] args) {        Thread et = new ExtendsThread();        Thread et2=new ExtendsThread2();        et.start();        et.setName("線程1");        Thread.currentThread().setName("主線程");        for(int i=1;i<=10;i++)        {            System.out.println(Thread.currentThread().getName()+":"+i);        }    }}

              測試結果

              如圖所示,線程1確實只進行到i=5的時候

              7、sleep(long millitime)

              強制線程進入休眠,單位是毫秒

              在指定時間內強制休眠

              需要注意的是,對某個線程使用sleep的話,該線程就會進入到掛起狀態,在指定時間掛起。相當于主動讓出了CPU的執行權。

              8、isAlive()

              判斷當前線程是否存活

              舉例如下

              public class ExtendsThread extends Thread{    @Override    public void run() {        super.run();        for(int i=0;i<=10;i++)        {            System.out.println(Thread.currentThread().getName()+":"+i+"-"+Thread.currentThread().isAlive());        }    }}public class ThreadTest {    public static void main(String[] args) {        Thread et = new ExtendsThread();        Thread et2=new ExtendsThread2();        et.start();        et.setName("線程1");        Thread.currentThread().setName("主線程");        for(int i=1;i<=10;i++)        {            System.out.println(Thread.currentThread().getName()+":"+i+"-"+Thread.currentThread().isAlive());        }        System.out.println(et.isAlive());    }}

              結果如下

              如圖所示,在代碼的最后,et所開啟的線程已經結束,所以此時打印出來的false

              線程的調度

              線程的進行主要是看時間片,一般情況下,多個線程都是并發,所以對于CPU的執行權一般是進行搶奪,高優先級的線程優先搶奪CPU的執行權。

              說到這里就不得不提到線程的優先等級(這里的優先級都是在線程誕生的時候就是設置好的,默認為5)

              >MAX_PRIORITY:10

              >MIN_PRIORITY:1

              >NORM_PRIORITY:5

              也有兩個方法是關于線程的優先級

              1、getPriority():返回線程優先級

              2、setPriority(int newPriority):改變線程的優先級

              高優先級搶占低優先級的線程的CPU執行權,但是是從概率上而言,高優先級的線程有更大的概率去執行CPU

              線程的五種狀態

              1、新建:當一個Thread類或其子類的聲明并創建時,新生線程處于此狀態

              2、就緒:當線程被start()之后,就會進入隊列等待CPU的時間片

              3、運行:獲得CPU資源,進入運行狀態,run定義了線程操作和功能

              4、阻塞:在某種情況下,被人為掛起或執行輸入輸出,讓出CPU的執行權

              5、死亡:線程完成了全部工作或被提前強制性中止(stop),或者出現異常導致結束,比如join()會使線程被掛起,造成線程阻塞

              線程的同步

              線程的安全問題(不一定出現線程安全問題)

              沒有sleep()出現時,錯誤的概率小,但是安全問題總是要解決的

              有可能會出現極端情況

              此時帶入一個場景,比如說一個線程代表一個窗口,一個售票窗口,線程每進行一次就掛起一次,會打印票號,但是如果正常進行,票號應該是連號,但是會出現如下情況

              代碼如下

              public class RunnalbeThread implements Runnable{    public static int num=30;    public static int tnum=1;    @Override    public void run() {        while(num!=0)        {            if(num>0)            {                num--;                tnum++;                System.out.println(Thread.currentThread().getName()+":"+tnum);                try {                    Thread.currentThread().sleep(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}public class ThreadTest {    public static void main(String[] args) {        Thread rt3 = new Thread(new RunnalbeThread());        Thread rt2 = new Thread(new RunnalbeThread());        Thread rt = new Thread(new RunnalbeThread());        rt2.start();        rt3.start();        rt.start();        rt3.setName("線程3");        rt.setName("線程1");        rt2.setName("線程2");    }}

              代碼測試結果如下

              很明顯的,會出現重號的現象

              原因:當某個線程操作票的過程中,尚未完成操作,另一個線程參與進來,也對車票進行操作(相當于是共享數據)

              如何解決

              加鎖

              當一個線程在操作共享數據的時候,其他線程不能參與,直到線程a操作結束,其他線程才能開始操作。即使a處于阻塞狀態,也不能被改變

              方法一:同步代碼塊

              synchronized(同步監視器){

              需要被同步的代碼}

              說明:操作共享數據的代碼,即為需要被同步的代碼

              同步監視器,俗稱鎖,可以隨意扔一個對象進去

              要求:多個線程要共用同一把鎖,不能設置多個鎖,此時不能使用匿名

              缺點:操作同步代碼時,僅能有一個線程操作,其他的都在等待,相當于是一個單線程操作過程,相對而言效率會很低

              此時會出現一個鎖不唯一的問題,由于鎖的創建在Thread的子類中,但是使用此方法創造進程需要newThread的子類的對象,此時會new出很多鎖,此時最好的解決方案就是把鎖進行static

              方法展示

              public class RunnalbeThread implements Runnable{    public static int num=30;    @Override    public void run() {        while(num!=0)        {            try {                Thread.currentThread().sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            synchronized (RunnalbeThread.class) {                if(num>0)                {                    num--;                    System.out.println(Thread.currentThread().getName()+":"+num);                }            }        }    }}public class ThreadTest {    public static void main(String[] args) {        Thread rt3 = new Thread(new RunnalbeThread());        Thread rt2 = new Thread(new RunnalbeThread());        Thread rt = new Thread(new RunnalbeThread());        rt2.start();        rt3.start();        rt.start();        rt3.setName("線程3");        rt.setName("線程1");        rt2.setName("線程2");    }}

              代碼中,把對于所有共享數據的操作全部都包起來了,達到監視的作用

              結果如下

              還有一個需要注意的點就是如果是用接口實現的方法創建的線程,可以考慮使用this的,之所以繼承法不能使用,是因為其依靠創造他本身的對象來創造線程,但是實現類只創造一個對象,其他對象都是利用Thread進行創造的。

              但是我的代碼中,監視器之后的鎖就不能使用this,因為在主函數中,我用的創建方法并不是一個對象傳入到Thread的構造器中,我使用了匿名對象,如果使用this,每一次的鎖都是不一樣的鎖,無法起到監視作用了

              同時,在我的代碼中,使用了synchronized (類名.class)這種方式,在這里需要注意的是,類本身也是一個對象,類僅加載一次,與每次new完之后出現的新對象不同。所以在我看來,類是一個完美的鎖,不會出現重復的現象。

              也需要注意對于同步代碼的包裝。要注意包裝的范圍,少包不能解決安全問題,包多了會影響效率,而且也容易出現新的問題。

              方式二:

              1、同步方法實現Runnable接口

              synchronized可以修飾方法,但是需要符合題意,一般情況下不建議使用

              在同步方法的內部,就和使用synchronized包起來是一個效果

              使用同步方法時,同步監視器就是this

              2、同步方法繼承Thread類的方法

              對于繼承法而言,很明顯不能直接加synchronized,加了synchronized之后,會自動使用this作為監視器,很顯然不行,此時應該將方法改成靜態

              總結同步方法

              1、仍涉及同步監視器,只是不需要顯式聲明

              2、非靜態的同步方法是this,靜態方法的監視器視為當前類本身

              衍生內容————單例設計模式

              1、懶漢式(線程安全)

              先來分析一下,在原本對于懶漢式的代碼中,線程安全可能會出現的部位

              public class Bank {    private Bank(){}    private static Bank instance=null;    public static Bank getInstance()    {        if(instance==null)        {            instance=new Bank();        }        //在此段就容易出現堵塞或者就緒,當多線程在此處參與時,設線程a、線程b        //a判斷了instance==null,已經進入了語句,此時CPU將執行權切換給了b或        //a由于某種原因阻塞了,那么此時可能就不僅僅創建了一個對象        return instance;    }}//而在關于單例式操作,同時滿足有多個線程,有共享數據這兩個條件,可以實現線程安全

              本質上就是線程a、b搶鎖,誰先搶到就誰先造

              如果想用同步方法,在本例中就可以直接將getInstance這個方法直接使用synchronized直接修飾,就可以解決線程安全問題

              如果想使用同步代碼塊,就可以使用synchronized將getInstance這個方法中的內容直接包裹,并且利用Bank.class對代碼進行監視(效率差)

              同步代碼塊——方法一

              public class Bank {    private Bank(){}    private static Bank instance=null;    public static Bank getInstance()    {        if(instance==null)        {            synchronized(Bank.class)            {                instance=new Bank();            }        }        return instance;    }}

              同步代碼塊——方法二

              public class Bank {    private Bank(){}    private static Bank instance=null;    public static Bank getInstance()    {        synchronized(Bank.class)        {            if(instance==null)            {                instance=new Bank();            }        }        return instance;    }}

              兩個方法在使用上的區別不大,都可以正常使用,但是實際上方法一的效率更高

              假設現在有線程1和線程2,當線程1率先搶到CPU控制權,先制造了對象,線程2在方法二中仍停留在synchronized語句上等待,一直到線程1制造完對象,線程2才能夠進入if,判斷失敗之后離開該方法,但是在方法一中,線程2先進入判斷,如果1已經造完對象了,那么線程2就會直接離開。線程2就不會再進入等待區。

              死鎖問題

              不同的線程分別占用了對象所需資源不放,都在等對方放棄,形成死鎖

              >不出現異常,不出現提示,所有的線程阻塞,不再進行

              使用同步的時候,一定要避免死鎖問題出現

              鎖的概念

              Lock實際上就是一個接口,需要有實現類

              Lock接口的具體使用,主要是對其實現類:Reentrantlock的使用

              Reentrantlock

              這個類有兩個構造器,有一個形參fair

              如果fair是true,就遵循先入先出,按照abc順序開鎖

              如果fair是false或者沒有參數,那么就是abc搶鎖,誰先搶到誰先開

              1、實例化Reentrantlock

              2、將同步代碼放到try中,在try首行調用Reentrantlock的對象調用Lock(),也可以調用解鎖,try-finally,其中不使用catch,只是想讓finally無論如果先給Lock解鎖,即使try過程有異常,也會給Lock解鎖

              (其實本質上也就是上鎖,只不過Lock需要手動開鎖,但是synchronized不需要,synchronized自動就會開鎖)

              synchronized和Lock的異同

              synchronized機制在執行完同步代碼塊后自動釋放同步監視器

              Lock需要手動開鎖,不然會一直鎖定一個線程不放

              基本都會使用synchronized,但是實際上更建議使用Lock

              sleep()和wait()的異同

              相同:都可以使當前線程進入阻塞

              不同:

              1、兩個方法聲明位置不同,Thread類中聲明sleep(),Object類中聲明wait()

              2、調用范圍不同,sleep()在任何場景都能調用,wait()必須使用在同步方法或者同步代碼塊中

              3、關于是否釋放同步監視器,如果二者都在同步中,sleep()不釋放鎖,但是wait()會釋放鎖

              責任編輯:

              標簽:

              相關推薦:

              精彩放送:

              新聞聚焦
              Top 中文字幕在线观看亚洲日韩