Java創(chuàng)建線程的3種方式和線程控制

一. 進程和線程

1. 什么是進程

進程是處于運行中的程序,并且具有一定的獨立功能,進程是系統(tǒng)進行資源分配和調(diào)度的獨立單位。

2. 進程的三個特征

(1)獨立性。進程是系統(tǒng)中獨立存在的實體,它可以擁有自己獨立的資源,每一個進程都擁有自己私有的地址空間。在沒有進過進程本身允許的情況下,一個用戶進程不可以直接訪問其他進程的地址空間。
(2)動態(tài)性。進程與程序的區(qū)別在于,程序只是一個靜態(tài)的指令集合,而進程是一個正在系統(tǒng)中活動的指令集合,在進程中增加了時間的概念。進程具有自己的生命周期和各種不同的狀態(tài),這些概念在程序中都是不具備的。
(3)并發(fā)性。多個進程可以在單個處理器上并發(fā)的執(zhí)行,多個進程之間不會互相影響。

操作系統(tǒng)多進程支持的理解:程序指令通過cpu執(zhí)行,在某個時間點只有一個程序的指令得到執(zhí)行,但是cpu執(zhí)行指令的速度非???,所以多個程序指令在cpu上輪流切換執(zhí)行的速度也很快,這樣在宏觀上感覺是多個程序在并發(fā)的執(zhí)行??梢赃@樣理解程序 的并發(fā),宏觀上并發(fā)執(zhí)行,微觀上順序執(zhí)行。

3. 什么是線程

線程是進程的組成部分,一個進程可以擁有多個線程,一個線程必須擁有一個父進程。線程可以擁有自己的的堆棧、自己的程序計數(shù)器和自己的局部變量,但不擁有系統(tǒng)資源,它與父進程的其他線程共享該進程的所擁有的全部資源。

二.創(chuàng)建線程的3種方式

1. 繼承Thread類創(chuàng)建線程

步驟如下:
a.定義Thread類的子類,并重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務。
b.創(chuàng)建Thread子類實例,即創(chuàng)建了線程對象。
c.調(diào)用線程對象的start()方法來創(chuàng)建并啟動線程。

public class FirstThread extends Thread{
    private int i;
    
    //重寫run()方法,run()方法的方法體就是線程的執(zhí)行體
    public void run(){
        for(;i<100;i++){
            //  當線程類繼承Thread類時,直接使用this即可獲取當前線程
            //Thread對象的getName()方法返回當前線程的名字
            //因此可以直接調(diào)用getName()方法返回當前線程的名字
            System.out.println(getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            //調(diào)用Thread類的currentThread()方法獲取當前線程
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20){
                //創(chuàng)建并啟動第一個線程
                new FirstThread().start();
                //創(chuàng)建并啟動第二個線程
                new FirstThread().start();
            }
        }
    }
}

2. 實現(xiàn)Runnable接口創(chuàng)建線程類

a.定義Runnable接口的實現(xiàn)類,并重寫該接口的run()方法,該run()方法的方法體就是線程的線程執(zhí)行體。
b.創(chuàng)建Runnable實現(xiàn)類的實例,并以此實例作為Thread類的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象。

public class SecondThread implements Runnable{
    private int i;
    
    //run()方法同樣是線程的執(zhí)行體
    @Override
    public void run(){
        for(;i<100;i++){
            //當線程實現(xiàn)Runnable接口時
            //如果想獲取當前線程,只能通過Thread.currentThread()方法
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20){
                SecondThread st=new SecondThread();
                //通過new Thread(target,name)方法創(chuàng)建新線程
                new Thread(st, "新線程1").start();
                new Thread(st, "新線程2").start();
            }
        }
    }
}

3. 使用Callable和Future創(chuàng)建線程

a.創(chuàng)建Callable接口的實現(xiàn)類,并實現(xiàn)call()方法,該call()方法將作為線程的執(zhí)行體,且該call()方法有返回值,再創(chuàng)建Callable實現(xiàn)類的實例。
b.使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
c.使用FutureTask對象作為Thread對象的target創(chuàng)建并啟動新線程。
d.使用FutureTask對象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值。

public class ThridThread {
    public static void main(String[] args) {
        //創(chuàng)建Callable對象
        ThridThread rt=new ThridThread();
        //先使用Lambda表達式創(chuàng)建Callable<Integer>對象
        //使用FutureTask來包裝Callable對象
        FutureTask<Integer> task=new FutureTask<>((Callable<Integer>)()->{
            int i=0;
            for(;i<100;i++){
                System.out.println(Thread.currentThread().getName()+" "+i);
            }
            //call()方法的返回值
            return i;
        });
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"循環(huán)變量i的值: "+i);
            if(i==20){
                //實質(zhì)是以Callable對象來創(chuàng)建并啟動線程
                new Thread(task,"有返回值的線程:").start();;
            }
        }
        try{
            //獲取線程的返回值
            System.out.println("子線程的返回值:"+task.get());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三.線程的生命周期

每個線程都要經(jīng)歷新建、就緒、運行、阻塞、死亡5種狀態(tài)。

  • 新建狀態(tài):就是通過new關鍵字創(chuàng)建線程對象時的狀態(tài)。

  • 就緒狀態(tài):即通過線程對象的start()方法啟動線程時對應的狀態(tài),此時線程并不一定馬上能進入運行狀態(tài),線程的運行由操作系統(tǒng)的調(diào)度程序進行線程的調(diào)度。

  • 運行狀態(tài):是指線程獲得cpu的執(zhí)行權(quán),線程正在執(zhí)行需要執(zhí)行的代碼。

  • 阻塞狀態(tài):當發(fā)生以下情況時線程進入阻塞狀態(tài)。

    1. 線程調(diào)用sleep()方法主動放棄所占有的處理器資源。
    2. 線程調(diào)用了一個阻塞式IO方法,在方法返回之前線程阻塞。
    3. 線程試圖獲得一個同步監(jiān)視器,但該監(jiān)視器正在被其他線程所持有。
    4. 線程正在等待某個通知(notify)。
    5. 程序調(diào)用了線程的suspend()方法將該線程掛起。
  • 死亡狀態(tài):線程會以以下三種方式結(jié)束,結(jié)束后的線程處于死亡狀態(tài)。

    1. run()方法和call()方法執(zhí)行完成,線程正常結(jié)束。
    2. 線程拋出一個未捕獲的Exception或Error。
    3. 直接調(diào)用線程的stop()方法來結(jié)束線程。

線程的狀態(tài)轉(zhuǎn)換圖如下:

線程的狀態(tài)轉(zhuǎn)換

注意點:

  1. 啟動一個線程是使用線程對象的start()方法,而不是直接調(diào)用run()方法。如果直接調(diào)用run()方法,則和普通的對象調(diào)用實例方法一樣,沒有啟動一個線程來執(zhí)行該方法。啟動一個線程只能對處于新建狀態(tài)的線程啟動,調(diào)用處于新建狀態(tài)的線程對象使用start()方法來啟動一個線程。如果對處于新建狀態(tài)的線程對象調(diào)用了run()方法或其他方法,則此線程對象就不再處于新建狀態(tài)了,以后調(diào)用該線程對象的start()方法將不會啟動一個線程。
  2. 對于處于死亡狀態(tài)的線程不能再次調(diào)用該線程的start()方法。程序只能對處于新建狀態(tài)的線程調(diào)用start()方法,對新建狀態(tài)的線程兩次調(diào)用start()方法也是錯誤的,會引起IllegalThreadStateException異常。

四.控制線程

1. join線程

join()方法時Java的Thread類提供的讓一個線程等待另一個線程完成的方法。當在某個程序的執(zhí)行流中調(diào)用其他線程的join()方法時,調(diào)用線程將被阻塞,直到被join()方法加入的join線程執(zhí)行完成為止。

public class JoinThread extends Thread {
    //提供一個有參構(gòu)造器,用于設置該線程的名字
    public JoinThread(String name){
        super(name);
    }
    //重寫run方法,定義線程的執(zhí)行體
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println(getName()+" "+i);
        }
    }
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<100;i++){
            if(i==20){
                JoinThread jt=new JoinThread("被join的線程");
                jt.start();
                //main線程調(diào)用了jt線程的join()方法,main線程必須等待jt線程執(zhí)行結(jié)束才會向下執(zhí)行
                jt.join();
            }
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}

2. 后臺線程

后臺線程(Daemon Thread)是在后臺運行的,它的任務是為其他的線程提供服務,也被稱為守護線程或精靈線程。
后臺線程的特征:如果所有的前臺線程都死亡,后臺線程會自動死亡。
調(diào)用Thread對象的setDaemon(true)方法可以將一個指定的線程設置為后臺線程。

public class DaemonThread extends Thread {
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println(getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        DaemonThread dt=new DaemonThread();
        //將此線程設置為后臺線程
        dt.setDaemon(true);
        dt.start();
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        //程序執(zhí)行到此處,前臺線程main線程結(jié)束
        //后臺線程也應該隨之結(jié)束
    }
}

3. 線程睡眠:sleep

Thread類的sleep()方法用來暫停線程的執(zhí)行,調(diào)用sleep()的線程將會進入阻塞狀態(tài)。Thread類的sleep()方法是Thread類的靜態(tài)方法。

public class SleepTest {
    public static void main(String[] args) throws Exception {
        for(int i=0;i<10;i++){
            System.out.println("當前時間:"+new Date());
            //調(diào)用sleep()方法讓當前線程暫停1s
            Thread.sleep(1000);
        }
    }
}

4. 線程讓步:yield

yield()方法也是Thread類提供的靜態(tài)方法,讓線程暫停執(zhí)行,與sleep()方法不同的是,yeild()方法不會將線程阻塞,當某個線程調(diào)用了yield()方法時,該線程會暫停執(zhí)行進入就緒狀態(tài),只有優(yōu)先級與當前線程相同或者優(yōu)先級比當前線程高的處于就緒狀態(tài)的線程才會獲得執(zhí)行的機會。

public class YieldTest extends Thread {
    public YieldTest(String name){
        super(name);
    }
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println(getName()+" "+i);
            //當i=20時,使用yield()方法讓當前線程讓步
            if(i==20){
                Thread.yield();
            }
        }
    }
    public static void main(String[] args) {
        //啟動兩個并發(fā)線程
        YieldTest yt1=new YieldTest("高級");
        //將yt1線程的設置為最高優(yōu)先級
        //yt1.setPriority(MAX_PRIORITY);
        yt1.start();
        YieldTest yt2=new YieldTest("低級");
        //將yt2線程的設置為最低優(yōu)先級
        //yt2.setPriority(MIN_PRIORITY);
        yt2.start();
    }

執(zhí)行上面的程序?qū)吹皆趇=20的時候yt1線程執(zhí)行yield()方法,因為yt2線程與yt1線程處于同一優(yōu)先級別,所以yt2線程將會獲得執(zhí)行權(quán),然后在yt2執(zhí)行到i=20的時候,yt2調(diào)用線程讓步方法yeild(),同樣的原因線程yt1將會獲得執(zhí)行權(quán)。

5. 改變線程的優(yōu)先級

Java中每個線程都有一定的優(yōu)先級,優(yōu)先級高的線程獲得執(zhí)行的機會多,而優(yōu)先級低的線程獲得執(zhí)行的機會少。對于創(chuàng)建的線程,Java默認的優(yōu)先級同創(chuàng)建它的父線程的優(yōu)先級相同。如果想改變線程的優(yōu)先級,則可以使用Thread類提供的setPriority(int newPriority)方法設置線程的優(yōu)先級,而getPriority()方法返回線程的優(yōu)先級。Java中的優(yōu)先級的參數(shù)范圍是1-10的整數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • 一、線程的生命周期 線程狀態(tài)轉(zhuǎn)換圖: 1、新建狀態(tài) 用new關鍵字和Thread類或其子類建立一個線程對象后,該線...
    我是嘻哈大哥閱讀 961評論 0 8
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,494評論 1 15
  • 一、進程和線程 進程 進程就是一個執(zhí)行中的程序?qū)嵗?,每個進程都有自己獨立的一塊內(nèi)存空間,一個進程中可以有多個線程。...
    阿敏其人閱讀 2,622評論 0 13
  • Java多線程學習 [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,987評論 1 18
  • 首先說明,這不是一篇影評,純屬感悟。 我覺得人都有以偏概全的毛病,無論對什么職業(yè),尤其是警察一類的官員,因為自己的...
    王小八16閱讀 285評論 1 0