前言
本文主要講解java多線程的基礎(chǔ),以及一些常用方法外邓。關(guān)于線程同步益咬、ExecutorService框架我會放到后續(xù)的文章進(jìn)行講解侈玄。
進(jìn)程與線程的區(qū)別
進(jìn)程
進(jìn)程簡單的來說就是在內(nèi)存中運(yùn)行的應(yīng)用程序婉刀,一個(gè)進(jìn)程可以啟動多個(gè)線程。
比如在windows中一個(gè)運(yùn)行EXE文件就是一個(gè)進(jìn)程拗馒。
線程
同一個(gè)線程中的進(jìn)程共用相同的地址空間路星,同時(shí)共享進(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-實(shí)現(xiàn)Runnable接口
除了繼承java.lang.Thread
類之外辞槐,我們還可以實(shí)現(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:
- 實(shí)現(xiàn)Runnable的方式需要將實(shí)現(xiàn)Runnable接口的類作為參數(shù)傳遞給Thread卜范,然后通過Thread類調(diào)用
Start()
方法來創(chuàng)建線程。- 這兩種方式都可以來創(chuàng)建線程鹿榜,至于選擇哪一種要看自己的需求海雪。直接繼承Thread類的話代碼要簡潔一些,但是由于java只支持單繼承舱殿,所以如果要繼承其他類的同時(shí)需要實(shí)現(xiàn)線程那就只能實(shí)現(xiàn)Runnable接口了奥裸,這里更推薦實(shí)現(xiàn)Runnable接口。
實(shí)際上如果我們查看Thread類的源碼我們會發(fā)現(xiàn)Thread是實(shí)現(xiàn)了Runnable接口的:
線程中常用的方法
序號 | 方法 | 介紹 |
---|---|---|
1 | public void start() |
使該線程執(zhí)行沪袭,java虛擬機(jī)會調(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)記為守護(hù)線程或用戶線程创倔,當(dāng)正在運(yùn)行線程都是守護(hù)線程時(shí),java虛擬機(jī)退出焚碌,該方法必須在啟動線程前調(diào)用。 |
5 | public final void join(long mills) |
等待該線程的終止時(shí)間最長為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)時(shí)寝殴,Thread1并沒有繼續(xù)執(zhí)行蒿叠,而是等待Thread0休眠結(jié)束釋放了對象鎖,Thread1才繼續(xù)執(zhí)行蚣常。
當(dāng)調(diào)用sleep()
方法時(shí)市咽,必須捕獲異常或者向上層拋出異常抵蚊。當(dāng)線程休眠時(shí)間滿時(shí)施绎,并不一定會馬上執(zhí)行,因?yàn)榇藭r(shí)有可能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("用時(shí):"+(end-open)+"毫秒");
}
}
輸出結(jié)果:
用時(shí):1毫秒
如果將 Thread.yield()注釋取消掉,輸出結(jié)果:
用時(shí):116毫秒
- 調(diào)用
yield()
方法是為了讓當(dāng)前線程交出CPU權(quán)限冈闭,讓CPU去執(zhí)行其他線程俱尼。它和sleep()
方法類似同樣是不會釋放鎖。但是yield()
不能控制具體的交出CUP的時(shí)間拒秘。并且它只能讓相同優(yōu)先級的線程獲得CPU執(zhí)行時(shí)間的機(jī)會号显。- 調(diào)用
yield()
方法不會讓線程進(jìn)入阻塞狀態(tài),而是進(jìn)入就緒狀態(tài)躺酒,它只需要等待重新獲取CPU的時(shí)間押蚤,這一點(diǎn)和sleep()
方法是不一樣的。
方法詳解- public final void join()
在很多情況下我們需要在子線程中執(zhí)行大量的耗時(shí)任務(wù)羹应,但是我們主線程又必須得等待子線程執(zhí)行完畢之后才能結(jié)束揽碘,這就需要用到 join()
方法了。join()
方法的作用是等待線程對象銷毀园匹,如果子線程執(zhí)行了這個(gè)方法雳刺,那么主線程就要等待子線程執(zhí)行完畢之后才會銷毀,請看下面這個(gè)例子:
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è)置是否為守護(hù)線程和判斷是否為守護(hù)線程掖桦。
notes:
- 守護(hù)線程依賴于創(chuàng)建他的線程,而用戶線程則不需要供汛。如果在
main()
方法中創(chuàng)建了一個(gè)守護(hù)線程枪汪,那么當(dāng)main方法執(zhí)行完畢之后守護(hù)線程也會關(guān)閉涌穆。而用戶線程則不會,在JVM中垃圾收集器的線程就是守護(hù)線程雀久。
優(yōu)雅的終止線程
有三種方法可以終止線程宿稀,如下:
- 使用退出標(biāo)識,使線程正常的退出赖捌,也就是當(dāng)
run()
方法完成后線程終止祝沸。 - 使用
stop()
方法強(qiáng)行關(guān)閉,這個(gè)方法現(xiàn)在已經(jīng)被廢棄越庇,不推薦使用 - 使用
interrupt()
方法終止線程罩锐。
具體的實(shí)現(xiàn)代碼我將在下一篇博文中將到。悦荒。
線程的優(yōu)先級
在操作系統(tǒng)中線程是分優(yōu)先級的唯欣,優(yōu)先級高的線程CPU將會提供更多的資源,在java中我們可以通過setPriority(int newPriority)
方法來更改線程的優(yōu)先級搬味。
在java中分為1~10這個(gè)十個(gè)優(yōu)先級境氢,設(shè)置不在這個(gè)范圍內(nèi)的優(yōu)先級將會拋出IllegalArgumentException
異常。
java中有三個(gè)預(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ù)會補(bǔ)充線程關(guān)閉萍聊、線程狀態(tài)、線程同步和有返回結(jié)果的多線程悦析。
個(gè)人博客地址:http://crossoverjie.top寿桨。
GitHub地址:[https://github.com/crossoverJie]