
前言
本文主要講解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接口的:

線程中常用的方法
| 序號 | 方法 | 介紹 |
|---|---|---|
| 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)雅的終止線程
有三種方法可以終止線程,如下:
- 使用退出標(biāo)識,使線程正常的退出,也就是當(dāng)
run()方法完成后線程終止。 - 使用
stop()方法強行關(guān)閉,這個方法現(xiàn)在已經(jīng)被廢棄,不推薦使用 - 使用
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]