為什么使用多線程
可以最大限度地利用CPU的空閑時間來處理其它任務。異步處理不同的任務,提高任務處理效率。
線程的五種狀態
1、新建狀態(New):線程對象創建后,就進入新建狀態。
2、就緒狀態(Runnable):就緒狀態又稱可執行狀態,線程被創建后通過調用start()方法,從而啟動線程。就緒狀態的線程,隨時有可能被CPU調度運行。
3、運行狀態(Running):線程獲取CPU權限進行執行。只有就緒狀態的線程才能進入運行狀態。
4、阻塞狀態(Blocked):線程因為某種原因放棄CPU使用權,停止運行。直到線程進入就緒狀態,才可以再到運行狀態。
阻塞狀態三種情況:
(1)、等待阻塞:通過調用線程的wait()方法,讓線程等待某工作完成
(2)、同步阻塞:線程獲取同步鎖synchronized同步鎖失敗(因為鎖正在被其它線程使用),進入同步阻塞。
(3)、其它阻塞:通過調用線程的sleep()、join()或發出I/O請求,線程進入阻塞狀態。當sleep()狀態超時、join()等待終止或超時、或者I/O處理完畢時,線程重新進入就休狀態。
5、死亡狀態(Dead):線程正常直行完成或者因為異常原因退出run()方法,該線程生命周期結束。
通過Thread和Runnable創建線程
Java的JDK開發包中,已經自帶了對多線程技術的支持,我們可以很方便的進行多線程編程。
實現多線程編程的方式主要有兩種,一種是繼承Thread類,另一種就是實現Runnable接口。而Thread和Runnable的關系就是Thread類實現了Runnable接口:
public class Thread implements Runnable {}
1、Runnable實現
java.lang.Runnable是一個接口,里面只定義了run()抽象方法。如果要實現多線程,可以實現Runnable接口,然后通過Thread thread = new Thread(new Xxx()),其中Xxx是實現Runnable接口的類。
public interface Runnable {
public abstract void run();
}
Runnable方式實現多線程
public class RunnableTest implements Runnable{
int num = 10;
@Override
public void run() {
for(int i=0;i<20;i++){
if(this.num > 0){
System.out.println(Thread.currentThread().getName()+" num:" +this.num-- );
}
}
}
}
class Test{
public static void main(String[] args){
RunnableTest runnable = new RunnableTest();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();//只有start()后才進入就緒狀態
thread2.start();
thread3.start();
}
}
運行結果
- Thread-0 num:10
- Thread-0 num:7
- Thread-2 num:8
- Thread-1 num:10
- Thread-1 num:9
- Thread-1 num:4
- Thread-1 num:3
- Thread-1 num:2
- Thread-1 num:1
- Thread-2 num:5
- Thread-0 num:6
結論:三個線程共享num變量,共同對num相減十次。這種情況也是出現“非線程安全的”原因,兩個線程可能同時獲取了同一變量值,同時進行操作,比如上面的線程0和線程1都打印了10。
2、Thread實現
java.lang.Thread是一個類,實現了Runnable接口。如果要實現多線程,需要繼承Thread類,然后通過創建實現類對象來啟動線程。
Thread方式實現多線程
class ThreadTest extends Thread{
int num = 10;
@Override
public void run() {
for(int i=0;i<20;i++){
if(this.num > 0){
System.out.println(this.getName() + " num:" + this.num--);
}
}
}
}
class Test{
public static void main(String[] args){
ThreadTest threadTest1 = new ThreadTest();
ThreadTest threadTest2 = new ThreadTest();
ThreadTest threadTest3 = new ThreadTest();
threadTest1.start();
threadTest2.start();
threadTest3.start();
}
}
運行結果
- Thread-0 num:10
- Thread-0 num:9
- Thread-0 num:8
- Thread-0 num:7
- Thread-0 num:6
- Thread-2 num:10
- Thread-2 num:9
- Thread-1 num:10
- Thread-1 num:9
- Thread-0 num:1
- ....
結論:通過繼承Thread類創建的線程,每個線程直接不會共享變量。每個線程都會各自對num進行10次相減。
實際上因為Thread實現了Runnable接口,所以我們也可以使用new Thead(theadTest)形式來啟動線程,這樣得到結果和實現Runnable接口結果時一樣的,因為所有線程都共享了同一個theadTest的num變量。
還有一個需要注意的點,就是如果多次調用start()方法,則會拋出異常:Exception in thread "main" java.lang.IllegalThreadStateException。
Thread對象交由其它線程執行
Thread的構造方法可以接收實現了Runnable接口的線程,而Thread類是Runnable接口的實現類,所以我們可以將一個Thread對象交由另一個線程執行,也就是另一個線程指向這個Thread實現類的run方法。
在說明這個問題前我們先介紹下Thread類的介個方法:
- currentThread:返回當前代碼塊被哪個線程執行。
- getName:返回當前線程名稱。
- isAlive():判斷當前線程是否處于活動狀態,活動狀態是指線程已經啟動并且還沒有執行完成(就緒狀態、運行狀態和阻塞狀態)。
下面是將線程1直接交由線程2來執行:
public class ThreadDemo {
public static void main(String[] args) {
//創建線程1
ThreadTest thread1 = new ThreadTest();
thread1.setName("rename thread1");
//創建線程2,并將線程1傳遞給線程2
Thread thread2 = new Thread(thread1);
thread2.setName("rename thread2");
thread2.start();
}
}
class ThreadTest extends Thread {
public ThreadTest() {
System.out.println("====1:" + Thread.currentThread().getName());
System.out.println("====1:" + Thread.currentThread().isAlive());
System.out.println("====2:" + this.getName());
System.out.println("====2:" + this.isAlive());
}
@Override
public void run() {
System.out.println("====3:" + Thread.currentThread().getName());
System.out.println("====3:" + Thread.currentThread().isAlive());
System.out.println("====4:" + this.getName());
System.out.println("====4:" + this.isAlive());
}
}
上面程序執行結果:
====1:main
====1:true
====2:Thread-0
====2:false
====3:rename thread2
====3:true
====4:rename thread1
====4:false
以====1開頭的打印內容是在ThreadTest構造方法中打印的,而調用ThreadTest構造方法是在main進程中的main方法中調用的,所以Thread.currentThread()表示的是main進程(因為ThreadTest代碼塊當前被main進程執行)。那====2的打印結果又說明什么呢,我們先看一下this.getName()好Thread.currentThread().getName()的區別。
Thread.currentThread()是指執行當前代碼塊的線程,所以Thread.currentThread().getName()給出的是執行當前代碼塊的線程名稱。而this是指當前線程對象,上面的例子也就是ThreadTest的實例,所以this.getName()給出的是當前對象的線程名稱。
通過上面的說明我們就知道了====2實際打印的是ThreadTest的一個實例,Thread-0是當我們創建一個Thread實例時,Java為我們提供的默認線程名稱,并且Thread-0此時并沒有運行(當前是main進程在執行)。
====3和=====4是在ThreadTest的run方法定義的,當啟動thread2線程時候會執行該run方法。====3同樣指的是運行當前代碼塊的線程,也就是"rename thread2"(因為我們在啟動thread2時為其重新定義名稱),并且thread2處于運行狀態(在執行當前代碼塊)。而====4的this此時還是值得ThreadTest的實例,也就是thread1線程,thread1也進行了重命名"rename thread1"(調用構造方法時還沒有進行重命名,所以當時的名稱是默認名稱Thread-0),并且當前thread1線程并沒有執行,thread1中的run方法是交由thread2線程執行的。
注意當前執行線程(Thread.currentThread())和當前線程對象(this)的區別。但是要清楚this.currentThread()和Thread.currentThread()是一樣。
上面的實例就說明:線程被創建后可以不執行,而是交由其它線程執行。其它線程啟動后,就會調用這個被創建的線程的run方法。
3、Runnable和Thread區別
- 通過實現Runnable接口能夠解決單繼承問題,如果實現Thread類則不能繼承其它類了。
- 通過Runnable接口方式實現多線程,能夠起到資源共享的目的,因為多個線程共用一個Runnable實現類,而Thread是繼承Runnable接口,每個Thread都會實現各自的Runnable接口。這種說法也不絕對,因為我們也可以將同一個Thread的實現類交由
new Thread(Runnable target)
來達到共享變量的目的。
4、Thread中start()和run()方法
start()和run()都是Thread中的方法,start()會啟動一個新線程,新線程會去執行相應的run()方法,start()不能重復調用。run()和普通成員方法一樣,單獨調用run()方法,不會啟動新線程,而是使用當前的線程執行run()方法(哪個線程調用的run方法,就由哪個線程來執行),這個run()和普通方法一樣,可以被重復調用。
run()源代碼:
@Override
public void run() {
if (target != null) {
//交由指定的線程來執行定義的run方法
target.run();
}
}
target是一個Runnable對象,run()方法直接調用Thread線程的Runnable的run()方法,并不會新建一個線程。
start()源代碼:
public synchronized void start() {
if (threadStatus != 0)//如果線程不是處于就緒狀態,拋出異常
throw new IllegalThreadStateException();
group.add(this);//將當前線程添加到線程組里
boolean started = false;
try {
start0();//啟動線程
started = true;//啟動標志位
} finally {
try {
if (!started) {
group.threadStartFailed(this);//啟動失敗,添加到失敗隊列里
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0(); //啟動線程的本地方法
start()通過調用本地start0()方法,啟動一個線程,新線程會調用run()方法。
線程常用方法
sleep()
Thread.sleep()的作用是“當前正在執行的線程”休眠指定時間,這個當前正在執行的線程指的是Thread.currentThread()。
getId()
this.getId()用于獲取線程的唯一標識。
yield()方法
yield()方法的作用是讓當前線程放棄CPU的使用權,讓其它任務執行。但是放棄的時間不固定,有可能馬上又獲取到CPU時間片。
測試下面代碼,查看注釋Thread.yield()前后的執行耗時。
class YieldThread extends Thread {
@Override
public void run() {
int count = 0;
long beginTime = System.currentTimeMillis();
for(int i= 0; i < 1000000; i++){
//Thread.yield();
count = count + i + 1;
}
long endTime = System.currentTimeMillis();
System.out.println("耗時" + (endTime - beginTime) + "毫秒!");
}
}
//注釋Thread.yield()打印結果:
耗時6毫秒!
//打開注釋
耗時820毫秒!
停止線程
Java中有以下三種方式來停止正在運行的線程。
- 使用退出標志,讓線程正常退出,也就是執行完了run方法后自動停止。
- 使用Thread類提供的stop()、suspend()或resume(),但是這三種方法是不建議使用的,因為它們可能產生不可預期的結果(可能一些清理工作得不到完成;對一些鎖定對象進行了“解鎖”,導致得不到同步處理,從而出現數據不一致的問題),并且這三個方法已經被標記為廢棄了。
- 使用interrupt方法來中斷線程,進而手動實現停止當前線程。
對于第一點沒什么說的,通過判斷退出標志位來退出run方法。第二點中Thread中自帶的stop()和suspend()由于不安全(從JDK2,開始不建議使用),所以已經過時不在建議使用了。線程中斷是目前停止線程的通用方式,也是我們這里要說的方式。
線程中斷方法
使用interrupt()方法并不會像使用for-break那樣立即退出循環,調用interrupt()方法只是將當前線程打了一個標記,也就是中斷標記。我們還需要加入一個判斷邏輯,來手動停止當線程。
Thread類提供了兩個方法來判斷當前線程中斷標記:
- Thread.interrupted():判斷當前線程是否已經中斷,當前線程指的是當前正在運行的線程(如果使用threadInstance.intrrupted()方式則指的是當前線程對象,有可能不是正在運行的線程)。Thread.interrupted()除了具有判斷線程是否中斷的功能,它還會清除線程中斷標記。也就是說如果當前線程中斷狀態為true,那么調用過interrupted后線程中斷狀態重新恢復到false。
- isInterrupted():判斷線程對象(有可能不是正在運行的線程)是否已經中斷,該方法不是靜態方法,所以不能通過Thread.currentThread調用。
注意isInterrupted和interrupted的區別:所以只能被線程對象調用,這時候線程對象有可能并沒有運行;另一個重要的區別在于isInterrupted方法不會清楚中斷狀態。
停止運行狀態的線程
停止運行中的線程,我們可以通過:判斷線程中斷標記+手動拋出異常的方式來停止線程。
class ThreadDemo extends Thread {
@Override
public void run() {
try {
while (true){
if(this.isInterrupted())
throw new InterruptedException();
//執行業務邏輯
}
}catch (InterruptedException e) {
System.out.println("進入Catch代碼塊,run方法執行完成,退出異常");
}
}
}
public class ThreadTest {
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
//中斷線程標記
threadDemo.interrupt();
}
}
注意try-catch需要在while循環外部,否則不會退出run方法。
停止阻塞狀態的線程
停止阻塞狀態的線程比較容易,因為它不需要我們判斷中斷標記。當對阻塞狀態中的線程調用interrupt()方法時,會自動拋出InterrupterdException異常,并且會清除中斷標記狀態值,也就是變為false。
public class ThreadTest {
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
threadDemo.interrupt();
}
}
class ThreadDemo extends Thread {
@Override
public void run() {
try {
while (true){
//業務執行邏輯
Thread.sleep(10000);
}
}catch (InterruptedException e) {
System.out.println("進入Catch代碼塊,run方法執行完成,退出異常");
}
}
}
需要注意try-catch在代碼中的維值,如果將異常處理放在while()中,這樣while(true)不會被停止。
暫停線程
暫停線程是指此線程能夠暫時停止,之后還能恢復運行。我們可以使用Thread類中的suspend()方法來暫停線程,使用resume()方法來恢復線程執行。
需要注意:suspend()、resume()方法和 stop()方法都已經廢棄了,因為它們有可能產生不可預知的后果
suspend()和resume()使用實例:
class MyThread extends Thread {
private long i = 0;
public long getI() {
return i;
}
@Override
public void run() {
while (true)
i++;
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(3000);
myThread.suspend();
System.out.println("i:" + myThread.getI());
Thread.sleep(3000);
System.out.println("i:" + myThread.getI());
myThread.resume();
Thread.sleep(3000);
myThread.suspend();
System.out.println("i:" + myThread.getI());
Thread.sleep(3000);
System.out.println("i:" + myThread.getI());
}
}
打印結果:
i:1629776063
i:1629776063
i:3296163993
i:3296163993
從上面的打印結果可以看到,線程確實暫停了,之后又重新恢復了。之所以將它們廢棄,主要是使用suspend()和resume()極易造成公共同步對象獨占,導致其它線程無法訪問公共對象。
synchronized void printString(String str) {
if(Thread.currentThread().getName().equals("TheadA")) {
System.out.println("被線程A獨占");
Thread.currentThread().suspend();
}
}
除了上面問題,suspend()和resume()方法如果使用不當,也會造成數據不同步,所以我們應該避免使用這兩個方法。
synchronized同步鎖
原理:每個對象都有且只有一個同步鎖,同步鎖依賴于對象存在。當調用某個對象的synchronized方法時,就獲取該對象的同步鎖。例如synchronized(obj)就是獲取了obj的同步鎖,不同線程對同步鎖訪問是互斥的。就是說一個時間點,對象的同步鎖只能被一個線程調用,其它線程如果要使用,需要等待正在使用同步鎖的線程釋放掉后才能使用。
synchronized規則:
- 當一個線程訪問某對象的synchronized方法或synchronized代碼塊時,其它線程對該對象的該synchronized方法或者synchronized代碼塊訪問將被受阻;
- 當一個線程訪問某個對象的synchronized方法或synchronized代碼塊時,其它線程可以訪問該對象的非synchronized方法或synchronized代碼塊;
- 當一個線程訪問某個對象的synchronized方法或synchronized代碼塊時,當其它對象訪問該對象的synchronized方法或synchronized代碼塊時,其它對象的線程將被受阻。
public class RunnableTest implements Runnable{
@Override
public void run() {
synchronized(this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" num:" + i );
}
}
}
}
class Test{
public static void main(String[] args){
RunnableTest runnable = new RunnableTest();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();//只有start()后才進入就緒狀態
thread2.start();
thread3.start();
}
}
運行結果
- Thread-0 num:0
- Thread-0 num:1
- Thread-0 num:2
- Thread-2 num:0
- Thread-2 num:1
- Thread-2 num:2
- Thread-1 num:0
- Thread-1 num:1
- Thread-1 num:2
結論:
當一個線程訪問對象的synchronized方法或者代碼塊,其它線程將會被阻塞。Thread0、Thread1、Thread2共用RunnableTest實現Runnable接口的同步鎖,當一個線程運行synchronized()代碼塊時候,其它線程需要等待正在運行的線程釋放同步鎖后才能運行。
再來看下使用Thread方式實現多線程的獲取同步鎖的執行流程
class ThreadTest extends Thread{
int num = 10;
@Override
public void run() {
synchronized(this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" num:" + i );
}
}
}
}
class Test{
public static void main(String[] args){
ThreadTest threadTest1 = new ThreadTest();
ThreadTest threadTest2 = new ThreadTest();
ThreadTest threadTest3 = new ThreadTest();
threadTest1.start();
threadTest2.start();
threadTest3.start();
}
}
運行結果
- Thread-0 num:0
- Thread-0 num:1
- Thread-0 num:2
- Thread-2 num:0
- Thread-1 num:0
- Thread-2 num:1
- Thread-1 num:1
- Thread-2 num:2
- Thread-1 num:2
結論:
發現并沒有我們之前說的Thread0、Thread1、Thread2阻塞順序執行,這個主要是和Thread形式創建多線程有關,trhreadTest1、trhreadTest2、trhreadTest3是三個不同的對象,它們是通過new ThreadTest()創建的三個對象,這里synchronized(this)是指的ThreadTest對象,所以threadTest1、threadTest2、threadTest3是獲取的三個不同的同步鎖。而上面使用RunnableTest方式實現的多線程,this是指的RunnableTest,這樣三個線程使用的是同一個對象的同步鎖。
當一個進程訪問對象的同步鎖時,其它線程可以訪問這個對象的非synchronize代碼塊
class ThreadTest2{
public void synMethod(){
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num: " + i);
}
}
}
public void nonSynMethod(){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num:" + i);
}
}
}
class Test{
public static void main(String[] args){
final ThreadTest2 threadTest = new ThreadTest2();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synMethod();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.nonSynMethod();
}
});
thread1.start();
thread2.start();
}
}
返回結果
- Thread-0 num:0
- Thread-0 num:1
- Thread-1 num:0
- Thread-0 num:2
- Thread-1 num:1
- Thread-1 num:2
結論:
thread1訪問對象的synchronize代碼塊,thread2訪問非synchronized代碼塊。thread2并沒有因為thread1受阻。
當一個線程訪問一個對象的synchronized方法或代碼塊,其它線程訪問這個對象的其它synchronized也是受阻的。
class ThreadTest2{
public void synMethod1(){
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num: " + i);
}
}
}
public void synMethod2(){
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num:" + i);
}
}
}
}
class Test{
public static void main(String[] args){
final ThreadTest2 threadTest = new ThreadTest2();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synMethod1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synMethod2();
}
});
thread1.start();
thread2.start();
}
}
返回結果
- Thread-0 num:0
- Thread-0 num:1
- Thread-0 num:2
- Thread-1 num:0
- Thread-1 num:1
- Thread-1 num:2
結論:thread1、thread2都會調用ThreadTest2的synchronized(this)代碼塊,而這個this都是ThreadTest2,所以線程2需要等到線程1執行完synchronized才能執行。
synchronized方法和synchronized代碼塊
synchronized方法是用synchronized修飾類方法,synchronized代碼塊是用synchronized修飾代碼塊的。synchronized代碼塊可以更精準的控制限制區域,有時效率也是比synchronized方法高的。
class ThreadTest2{
public synchronized void synMethod1(){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num: " + i);
}
}
public void synMethod2(){
//this獲取當前對象的同步鎖,如果修改成xxx,則獲取xxx的同步鎖
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num:" + i);
}
}
}
}
實例鎖和全局鎖
- 實例鎖:如果鎖在某一個實例上面,那么該鎖就是實例鎖。如果這個類是單例,那么這個鎖也具有全局鎖的概念。實例鎖使用synchronized關鍵字。
- 全局鎖:如果鎖針對的是一個類上面,無論多少個實例共享這個鎖。全局鎖使用static synchronized,或者鎖在該類的class或classloader上面。
class SomeLock{
public synchronized void intanceLockA(){}
public synchronized void instanceLockB(){};
public static synchronized void globalLockA(){};
public static synchronized void globalLockB(){};
}
- x.instaceLockA()和x.instanceLockB(),二者不能同時被訪問,因為二者都是訪問的都是x的實例鎖。
- x.instaceLockA()和y.instaceLockA(),二者可以同時被訪問,因為二者訪問的不是同一個對象的鎖。
- x.globalLocckA()和y.globalLockB(),二者不能同時訪問,因為y.globalLockB()相當于SomeLock.globalLockB(),x.globalLockA()相當于SomeLock.globalLockA(),二者使用的是同一個同步鎖,所以不能同時被訪問。
- x.instaceLocakA()和b.globalLockA(),二者可以同時被訪問,因為一個是示例的鎖,一個是類的鎖。
線程的等待與喚醒
線程的等待與喚醒使用了Object類中的wait()、wait(long timeout)、wait(long timeout,int nanos)、notify()、notifyAll()
- wait():使線程進入等待狀態(等待阻塞),直到其它線程調用該對象的 notify()或notifyAll(),當前線程會被喚醒(進入就緒狀態)。
- wait(long timeout):使線程進入等待狀態(等待阻塞),直到其它線程調用該對象的notify()或notifyAll()或超過了指定時間,當前線程會被喚醒(進入就緒狀態)。
- wait(long timeout,int nanos):使線程進入等待狀態(等待阻塞),直到其它線程調用該對象的notify()或notifyAll()或超過了指定時間或被其它線程中斷,當前線程會被喚醒(進入就緒狀態)。
- notify():喚醒在此對象監視器(同步鎖的實現原理)上等待的單個線程。
- notifyAll():喚醒在此對象監視器上等待的多個線程。
注意:
wait()的作用是讓當前線程等待,當前線程指的是正在cpu運行的線程,而不是調用wait()方法的線程。wait()、notify()、notifyAll()都是屬于Object類下邊的方法,之所以在Object下面而沒有在Thread類下面,主要原因就是同步鎖。
wait()和notify()都是對對象的同步鎖進行操作,同步鎖是對象持有的,并且每個對象有且僅有一個。
線程讓步yield()
yield()的作用是讓步。讓當前線程由運行狀態進入就緒狀態,從而讓其他具有高優先級的線程獲取cpu執行。但是并不會保證當前線程調用yield()后,其它同等級線程一定獲取到cpu執行權。也有可能當前線程又進入到運行狀態。
yield()與wait()的區別
- wait()會由運行狀態進入等待狀態(阻塞狀態),而yield()會從運行狀態進入就緒狀態。
- wait()會釋放對象的同步鎖,而yield()是不會釋放對象的同步鎖的。
線程休眠sleep()
sleep()在Thread.class類中定義,讓當前線程由運行狀態進入休眠狀態(阻塞狀態)。sleep()需要指定休眠時間,線程休眠時間會大于等于該休眠時間;線程被重新喚醒時會由阻塞狀體進入就緒狀態。
sleep()與wait()區別
sleep()和wait()都會讓線程由運行狀態進入阻塞狀態,但是wait()會釋放對象同步鎖,而sleep()不會釋放同步鎖。
線程join()
join()在Thread.class類中定義,讓主線程等待子線程結束后才能繼續運行。
// 主線程
public class Father extends Thread {
public void run() {
Son s = new Son();
s.start();
s.join();
...
}
}
// 子線程
public class Son extends Thread {
public void run() {
...
}
}
Son線程是在Father線程中創建的,并且調用了s.join(),這樣Father線程要等到Son線程執行完成后,才會執行。可以查看下join()的源代碼:
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
join()中通過wait()進行等待,所以即使是子線程調用的join(),而真實等待的是正在執行的父進程。
線程優先級
在操作系統中,線程可以劃分優先級,優先級較高的線程被CPU優先執行。設置線程優先級就是幫助“線程規劃器”確定下次選哪一個線程來優先執行。
java中線程優先級從1~10,默認是5。Thead類提供了3個常量預定義優先級的值:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
線程優先級具有繼承性,這里的繼承性是指比如在A線程中啟動B線程,那么B線程就有與A線程同樣的優先級。
需要注意的是,高優先級的線程總是大部分都會先執行完成,但是并不代表高優先級的線程執行完成后,再去執行低優先級的線程。高優先級,只說明CPU盡量將執行資源給優先級比較高的線程。
守護線程
在Java線程有兩種線程,非守護線程(又稱為用戶線程)和守護線程(Daemon)。
守護線程是一種特殊的線程,它的特性就是陪伴,當進程中不存在非守護線程了,則守護線程自動銷毀。比如GC線程就是典型的守護線程,當進程中沒有非守護線程了,則垃圾回收線程自動銷毀。
比如下面測試用例:
class DaemonThread extends Thread {
@Override
public void run() {
int i =0;
try {
while (true) {
System.out.println(i++);
Thread.sleep(1000);
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
DaemonThread daemonThread = new DaemonThread();
daemonThread.setDaemon(true);
daemonThread.start();
Thread.sleep(5000);
System.out.println("main線程退出,daemonThread也不會執行了");
}
}
打印結果:
0
1
2
3
4
main線程退出,daemonThread也不會執行了
因為DaemonThread是守護線程,而main線程為非守護線程,當main線程退出后,daemonThread也會退出。
關注我
歡迎關注我的公眾號,會定期推送優質技術文章,讓我們一起進步、一起成長!
公眾號搜索:data_tc
或直接掃碼:??