Java多線程(一)多線程基礎(chǔ)

前言

本文主要講解java多線程的基礎(chǔ),以及一些常用方法。關(guān)于線程同步、ExecutorService框架我會放到后續(xù)的文章進(jìn)行講解。


進(jìn)程與線程的區(qū)別

進(jìn)程

進(jìn)程簡單的來說就是在內(nèi)存中運行的應(yīng)用程序,一個進(jìn)程可以啟動多個線程。
比如在windows中一個運行EXE文件就是一個進(jìn)程。

線程

同一個線程中的進(jìn)程共用相同的地址空間,同時共享進(jìn)程所擁有的內(nèi)存和其他資源。


線程Demo-繼承Thread類

首先我們我們繼承java.lang.Thread類來創(chuàng)建線程。

package top.crosssoverjie.study.Thread;

public class TestThread {
    public static void main(String[] args) {
        System.out.println("主線程ID是:" + Thread.currentThread().getId());
        MyThread my = new MyThread("線程1");
        my.start() ;
        
        MyThread my2 = new MyThread("線程2") ;
        /**
         * 這里直接調(diào)用my2的run()方法。
         */
        my2.run() ;
    }

}

class MyThread extends Thread {
    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("名字:" + name + "的線程ID是="
                + Thread.currentThread().getId());
    }

}

輸出結(jié)果:

主線程ID是:1
名字:線程2的線程ID是=1
名字:線程1的線程ID是=9

由輸出結(jié)果我們可以得出以下結(jié)論:

  • my和my2的線程ID不相同,my2和主線程ID相同。說明直接調(diào)用run()方法不會創(chuàng)建新的線程,而是在主線程中直接調(diào)用的run()方法,和普通的方法調(diào)用沒有區(qū)別。
  • 雖然my的start()方法是在my2的run()方法之前調(diào)用,但是卻是后輸出內(nèi)容,說明新建的線程并不會影響主線程的執(zhí)行。

線程Demo-實現(xiàn)Runnable接口

除了繼承java.lang.Thread類之外,我們還可以實現(xiàn)java.lang.Runnable接口來創(chuàng)建線程。

package top.crosssoverjie.study.Thread;

public class TestRunnable {
    public static void main(String[] args) {
        System.out.println("主線程的線程ID是"+Thread.currentThread().getId());
        MyThread2 my = new MyThread2("線程1") ;
        Thread t = new Thread(my) ;
        t.start() ;
        
        MyThread2 my2 = new MyThread2("線程2") ;
        Thread t2 = new Thread(my2) ;
        /**
         * 方法調(diào)用,并不會創(chuàng)建線程,依然是主線程
         */
        t2.run() ;
    }
}

class MyThread2 implements Runnable{
    private String name ;
    public MyThread2(String name){
        this.name = name ;
    }

    @Override
    public void run() {
        System.out.println("線程"+name+"的線程ID是"+Thread.currentThread().getId());
    }
    
    
}

輸出結(jié)果:

主線程的線程ID是1
線程線程2的線程ID是1
線程線程1的線程ID是9

notes:

  • 實現(xiàn)Runnable的方式需要將實現(xiàn)Runnable接口的類作為參數(shù)傳遞給Thread,然后通過Thread類調(diào)用Start()方法來創(chuàng)建線程。
  • 這兩種方式都可以來創(chuàng)建線程,至于選擇哪一種要看自己的需求。直接繼承Thread類的話代碼要簡潔一些,但是由于java只支持單繼承,所以如果要繼承其他類的同時需要實現(xiàn)線程那就只能實現(xiàn)Runnable接口了,這里更推薦實現(xiàn)Runnable接口。

實際上如果我們查看Thread類的源碼我們會發(fā)現(xiàn)Thread是實現(xiàn)了Runnable接口的:


Thread源碼

線程中常用的方法

序號 方法 介紹
1 public void start() 使該線程執(zhí)行,java虛擬機會調(diào)用該線程的run()方法。
2 public final void setName(String name) 修改線程名稱。
3 public final void setPriority(int privority) 修改線程的優(yōu)先級。
4 public final void setDaemon(false on) 將該線程標(biāo)記為守護線程或用戶線程,當(dāng)正在運行線程都是守護線程時,java虛擬機退出,該方法必須在啟動線程前調(diào)用。
5 public final void join(long mills) 等待該線程的終止時間最長為mills毫秒。
6 public void interrupt() 中斷線程。
7 public static boolean isAlive() 測試線程是否處于活動狀態(tài)。如果該線程已經(jīng)啟動尚未終止,則為活動狀態(tài)。
8 public static void yield() 暫停當(dāng)前線程執(zhí)行的對象,并執(zhí)行其他線程。
9 public static void sleep(long mills) 在指定毫秒數(shù)內(nèi),讓當(dāng)前執(zhí)行的線程休眠(暫停)。
10 public static Thread currentThread() 返回當(dāng)前線程的引用。

方法詳解- public static void sleep(long mills)

package top.crosssoverjie.study.Thread;

public class TestSleep {

    private int i = 10 ;
    private Object ob = new Object() ;
    
    public static void main(String[] args) {
        TestSleep t = new TestSleep() ;
        MyThread3 thread1 = t.new MyThread3() ;
        MyThread3 thread2 = t.new MyThread3() ;
        thread1.start() ;
        thread2.start() ;
    }
    
    class MyThread3 extends Thread{
        @Override
        public void run() {
            synchronized (ob) {
                i++ ;
                System.out.println("i的值:"+i);
                System.out.println("線程:"+Thread.currentThread().getName()+"進(jìn)入休眠狀態(tài)");
                try {
                    Thread.currentThread().sleep(1000) ;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("線程:"+Thread.currentThread().getName()+"休眠結(jié)束");
                i++;
                System.out.println("i的值>:"+i);
            }
        }
    }
    
}

輸出結(jié)果:

i的值:11
線程:Thread-0進(jìn)入休眠狀態(tài)
線程:Thread-0休眠結(jié)束
i的值>:12
i的值:13
線程:Thread-1進(jìn)入休眠狀態(tài)
線程:Thread-1休眠結(jié)束
i的值>:14

由輸出結(jié)果我們可以得出:

  • 當(dāng)Thread0進(jìn)入休眠狀態(tài)時,Thread1并沒有繼續(xù)執(zhí)行,而是等待Thread0休眠結(jié)束釋放了對象鎖,Thread1才繼續(xù)執(zhí)行。
    當(dāng)調(diào)用sleep()方法時,必須捕獲異?;蛘呦蛏蠈訏伋霎惓?。當(dāng)線程休眠時間滿時,并不一定會馬上執(zhí)行,因為此時有可能CPU正在執(zhí)行其他的任務(wù),所以調(diào)用了sleep()方法相當(dāng)于線程進(jìn)入了阻塞狀態(tài)。

方法詳解- public static void yield()

package top.crosssoverjie.study.Thread;

public class Testyield {
    public static void main(String[] args) {
        MyThread4 my = new MyThread4() ;
        my.start() ;
    }
}
class MyThread4 extends Thread{
    @Override
    public void run() {
        long open = System.currentTimeMillis();
        int count= 0 ;
        for(int i=0 ;i<1000000;i++){
            count= count+(i+1);
//            Thread.yield() ;
        }
        long end = System.currentTimeMillis();
        System.out.println("用時:"+(end-open)+"毫秒");
    }
}

輸出結(jié)果:
用時:1毫秒
如果將 Thread.yield()注釋取消掉,輸出結(jié)果:
用時:116毫秒

  • 調(diào)用yield()方法是為了讓當(dāng)前線程交出CPU權(quán)限,讓CPU去執(zhí)行其他線程。它和sleep()方法類似同樣是不會釋放鎖。但是yield()不能控制具體的交出CUP的時間。并且它只能讓相同優(yōu)先級的線程獲得CPU執(zhí)行時間的機會。
  • 調(diào)用yield()方法不會讓線程進(jìn)入阻塞狀態(tài),而是進(jìn)入就緒狀態(tài),它只需要等待重新獲取CPU的時間,這一點和sleep()方法是不一樣的。

方法詳解- public final void join()

在很多情況下我們需要在子線程中執(zhí)行大量的耗時任務(wù),但是我們主線程又必須得等待子線程執(zhí)行完畢之后才能結(jié)束,這就需要用到 join()方法了。join()方法的作用是等待線程對象銷毀,如果子線程執(zhí)行了這個方法,那么主線程就要等待子線程執(zhí)行完畢之后才會銷毀,請看下面這個例子:

package top.crosssoverjie.study.Thread;

public class Testjoin {
    public static void main(String[] args) throws InterruptedException {
        new MyThread5("t1").start() ;
        for (int i = 0; i < 10; i++) {
            if(i == 5){
                MyThread5 my =new MyThread5("t2") ;
                my.start() ;
                my.join() ;
            }
            System.out.println("main當(dāng)前線程:"+Thread.currentThread().getName()+" "+i);
        }
    }
}
class MyThread5 extends Thread{
    
    public MyThread5(String name){
        super(name) ;
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+" "+i);
        }
    }
}

輸出結(jié)果:

main當(dāng)前線程:main 0
當(dāng)前線程:t1 0
當(dāng)前線程:t1 1
main當(dāng)前線程:main 1
當(dāng)前線程:t1 2
main當(dāng)前線程:main 2
當(dāng)前線程:t1 3
main當(dāng)前線程:main 3
當(dāng)前線程:t1 4
main當(dāng)前線程:main 4
當(dāng)前線程:t2 0
當(dāng)前線程:t2 1
當(dāng)前線程:t2 2
當(dāng)前線程:t2 3
當(dāng)前線程:t2 4
main當(dāng)前線程:main 5
main當(dāng)前線程:main 6
main當(dāng)前線程:main 7
main當(dāng)前線程:main 8
main當(dāng)前線程:main 9

如果我們把join()方法注釋掉之后:

main當(dāng)前線程:main 0
當(dāng)前線程:t1 0
main當(dāng)前線程:main 1
當(dāng)前線程:t1 1
main當(dāng)前線程:main 2
當(dāng)前線程:t1 2
main當(dāng)前線程:main 3
當(dāng)前線程:t1 3
main當(dāng)前線程:main 4
當(dāng)前線程:t1 4
main當(dāng)前線程:main 5
main當(dāng)前線程:main 6
main當(dāng)前線程:main 7
main當(dāng)前線程:main 8
main當(dāng)前線程:main 9
當(dāng)前線程:t2 0
當(dāng)前線程:t2 1
當(dāng)前線程:t2 2
當(dāng)前線程:t2 3
當(dāng)前線程:t2 4

由上我們可以得出以下結(jié)論:

  • 在使用了join()方法之后主線程會等待子線程結(jié)束之后才會結(jié)束。

方法詳解- setDaemon(boolean on),getDaemon()

用來設(shè)置是否為守護線程和判斷是否為守護線程。
notes:

  • 守護線程依賴于創(chuàng)建他的線程,而用戶線程則不需要。如果在main()方法中創(chuàng)建了一個守護線程,那么當(dāng)main方法執(zhí)行完畢之后守護線程也會關(guān)閉。而用戶線程則不會,在JVM中垃圾收集器的線程就是守護線程。

優(yōu)雅的終止線程

有三種方法可以終止線程,如下:

  1. 使用退出標(biāo)識,使線程正常的退出,也就是當(dāng)run()方法完成后線程終止。
  2. 使用stop()方法強行關(guān)閉,這個方法現(xiàn)在已經(jīng)被廢棄,不推薦使用
  3. 使用interrupt()方法終止線程。

具體的實現(xiàn)代碼我將在下一篇博文中將到。。

線程的優(yōu)先級

在操作系統(tǒng)中線程是分優(yōu)先級的,優(yōu)先級高的線程CPU將會提供更多的資源,在java中我們可以通過setPriority(int newPriority)方法來更改線程的優(yōu)先級。
在java中分為1~10這個十個優(yōu)先級,設(shè)置不在這個范圍內(nèi)的優(yōu)先級將會拋出IllegalArgumentException異常。
java中有三個預(yù)設(shè)好的優(yōu)先級:

  • public final static int MIN_PRIORITY = 1;
  • public final static int NORM_PRIORITY = 5;
  • public final static int MAX_PRIORITY = 10;

參考

java多線程思維圖


總結(jié)

以上就是我總結(jié)的java多線程基礎(chǔ)知識,后續(xù)會補充線程關(guān)閉、線程狀態(tài)、線程同步和有返回結(jié)果的多線程。

個人博客地址:http://crossoverjie.top。
GitHub地址:[https://github.com/crossoverJie]

最后編輯于
?著作權(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)容

  • 前言 多線程并發(fā)編程是Java編程中重要的一塊內(nèi)容,也是面試重點覆蓋區(qū)域,所以學(xué)好多線程并發(fā)編程對我們來說極其重要...
    嘟爺MD閱讀 7,427評論 21 272
  • 線程概述 線程與進(jìn)程 進(jìn)程 ?每個運行中的任務(wù)(通常是程序)就是一個進(jìn)程。當(dāng)一個程序進(jìn)入內(nèi)存運行時,即變成了一個進(jìn)...
    閩越布衣閱讀 1,108評論 1 7
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,606評論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,117評論 1 18
  • 寫在前面的話: 這篇博客是我從這里“轉(zhuǎn)載”的,為什么轉(zhuǎn)載兩個字加“”呢?因為這絕不是簡單的復(fù)制粘貼,我花了五六個小...
    SmartSean閱讀 4,966評論 12 45

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