線程睡眠的方法:
在 Java 中,讓線程休眠的方法有很多,這些方法大致可以分為兩類,一類是設置時間,在一段時間后自動喚醒,而另一個類是提供了一對休眠和喚醒的方法,在線程休眠之后,可以在任意時間對線程進行喚醒。
線程睡眠的方法有以下 5 個:
- Thread.sleep
- TimeUnit
- wait
- Condition
- LockSupport
其中 sleep 和 TimeUnit 是讓線程睡眠一段時間后自動喚醒,而 wait、Condition、LockSupport 提供了一對休眠和喚醒線程的方法,可以實現(xiàn)任意時刻喚醒某個線程。
方法1:Thread.sleep
Thread
類的sleep()
方法用于在指定的時間內(nèi)睡眠線程。
java中sleep()方法的語法Thread
類為睡眠線程提供了兩種方法:
public static void sleep(long miliseconds)throws InterruptedException
public static void sleep(long miliseconds, int nanos)throws InterruptedException
以上程序的執(zhí)行結(jié)果如下圖所示:?
class TestSleepMethod1 extends Thread {
public void run() {
for (int i = 1; i < 5; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println(i);
}
}
public static void main(String args[]) {
TestSleepMethod1 t1 = new TestSleepMethod1();
TestSleepMethod1 t2 = new TestSleepMethod1();
t1.start();
t2.start();
}
}
以上程序的執(zhí)行結(jié)果如下所示:
1 1 2 2 3 3 4 4
方法2:TimeUnit
sleep 方法因為要傳遞一個毫秒類型的參數(shù),因此在設置大一點的時間時比較麻煩,比如設置 1 小時或 1 天時,此時我們就可以使用 TimeUnit 來替代 sleep 方法實現(xiàn)休眠。?
TimeUnit 的功能和 sleep 一樣,讓線程休眠 N 個單位時間之后自動喚醒,它的基礎(chǔ)用法如下:
以上程序的執(zhí)行結(jié)果如下圖所示:?
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("線程執(zhí)行:" + LocalDateTime.now());
try {
TimeUnit.SECONDS.sleep(1); // 休眠 1s
//TimeUnit.DAYS.sleep(1); // 休眠 1 天
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程結(jié)束:" + LocalDateTime.now());
}
};
t1.start();
?當我們查看 TimeUnit 源碼時就會發(fā)現(xiàn),它的底層是基于 Thread.sleep 方法實現(xiàn)的,其實現(xiàn)源碼如下:?
方法3:wait
wait/notify/notifyAll 都來自于 Object 類,其中:
- wait() / wait(long timeout):表示讓當前線程進入休眠狀態(tài)。
- notify():喚醒當前對象上的一個休眠線程。
- notifyAll():喚醒當前對象上的所有休眠線程。
其中 wait() 方法表示讓當前線程無限期等待下去,直到遇到 notify/notifyAll 方法時才會被喚醒,而 wait(long timeout) 表示接收一個 long 類型的超時時間,如果沒有遇到 notify/notifyAll 會在 long 毫秒之后自動喚醒,如果遇到了 notify/notifyAll 方法會立即被喚醒。 它的基礎(chǔ)用法如下:
Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
try {
// 讓當前線程休眠
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
synchronized (lock) {
lock.notify(); // 喚醒當前對象上一個休眠線程
// lock.notifyAll(); // 喚醒當前對象上所有休眠的線程
}
需要注意的是 wait/notify/notifyAll 在使用時必須要配合 synchronized 一起使用,否則程序執(zhí)行會報錯。
方法4:Condition
Condition 作為 wait 的升級版,它提供的常用方法有以下幾個:
- await():讓當前線程進入等待狀態(tài),直到被通知(signal)或者被中斷時才會繼續(xù)執(zhí)行。
- awaitUninterruptibly():讓當前線程進入等待狀態(tài),直到被通知才會被喚醒,它對線程的中斷通知不做響應。
- await(long time, TimeUnit unit):在 await() 方法的基礎(chǔ)上添加了超時時間,如果過了超時時間還沒有遇到喚醒方法則會自動喚醒并恢復執(zhí)行。
- awaitUntil(Date deadline):讓當前線程進入等待狀態(tài),如果沒有遇到喚醒方法也會在設置的時間之后自動喚醒。
- signal():喚醒一個等待在 Condition 上的線程。
- signalAll():喚醒等待在 Condition 上所有的線程。
它的基本用法如下:
import java.time.LocalDateTime;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
public static void main(String[] args) throws InterruptedException {
// 創(chuàng)建鎖
final Lock lock = new ReentrantLock();
// 創(chuàng)建 Condition
final Condition condition = lock.newCondition();
new Thread(() -> {
System.out.println("線程執(zhí)行:" + LocalDateTime.now());
lock.lock(); // 得到鎖
try {
// 休眠線程
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 釋放鎖
}
System.out.println("線程結(jié)束:" + LocalDateTime.now());
}).start();
Thread.sleep(1000);
lock.lock(); // 得到鎖
try {
// 喚醒線程
condition.signal();
} finally {
lock.unlock(); // 釋放鎖
}
}
}
相比于 wait 方法,Condition 對象更加靈活,因為它可以在一把鎖上定義多個 Condition 對象進行使用,
如下代碼所示:
// 創(chuàng)建鎖
final Lock lock = new ReentrantLock();
// 創(chuàng)建 Condition 1
final Condition condition = lock.newCondition();
// 創(chuàng)建 Condition 2
final Condition condition2 = lock.newCondition();
// ......
方法5:LockSupport
LockSupport 是更加底層的操作線程休眠和喚醒的對象,它提供了兩個常用的方法:
- LockSupport.park():休眠當前線程。
- LockSupport.unpark(Thread thread):喚醒一個指定的線程。
它的基礎(chǔ)用法如下:
Thread t1 = new Thread(() -> {
System.out.println("線程1休眠");
LockSupport.park(); // 休眠線程
System.out.println("線程1執(zhí)行結(jié)束");
}, "線程1");
t1.start();
Thread t2 = new Thread(() -> {
System.out.println("線程2休眠");
LockSupport.park(); // 休眠線程
System.out.println("線程2執(zhí)行結(jié)束");
}, "線程2");
t2.start();
Thread t3 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("喚醒線程1");
LockSupport.unpark(t1); // 喚醒線程1
}, "線程3");
t3.start();
以上程序的執(zhí)行結(jié)果如下圖所示:
方法總結(jié)
Thread.sleep 和 TimeUnit 是讓線程休眠并在一段時間后自動喚醒,而 wait、Condition、LockSupport 提供了休眠和喚醒線程的方法,其中 Condition 為 wait 方法的升級版,而 LockSupport 是更底層的讓線程休眠和喚醒的方法,它可以實現(xiàn)喚醒某個指定的線程,這是其它方法所不具備的(功能)。
線程睡眠的作用
線程睡眠可以有效的控制線程的執(zhí)行時間,可以讓CPU資源分配更加均衡,提高程序的運行效率和穩(wěn)定性。
在并發(fā)編程中,線程經(jīng)常會被調(diào)度器打斷,通過線程睡眠,可以讓該線程“放棄”一段時間的CPU執(zhí)行權(quán),避免CPU資源浪費和競爭。另外,線程睡眠還可以用來模擬線程執(zhí)行中的等待時間,例如Java中的定時器和倒計時器的實現(xiàn),都離不開線程睡眠。
線程睡眠的注意事項
在使用線程睡眠時,需要注意以下幾個問題:
1. InterruptedException異常
在調(diào)用線程睡眠方法時,需要捕獲InterruptedException異常。InterruptedException是一個檢查異常,它是在調(diào)用線程的interrupt()方法后,拋出的一種異常。
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
// 主線程等待子線程執(zhí)行完畢
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
2. 線程睡眠不會釋放鎖
在線程睡眠期間,該線程所持有的鎖并不會被釋放,因此,其他線程仍將被阻塞。
synchronized (obj) {
System.out.println("獲取obj鎖");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("釋放obj鎖");
}
3. 睡眠時間應盡量短
線程睡眠的時間應盡量短,可以根據(jù)實際需要調(diào)整線程睡眠的時間。如果睡眠時間過長,會導致程序的響應時間變慢,影響用戶體驗。另外,需要避免不必要的線程睡眠,以免影響程序的運行效率。
4. 時間單位要選對
在使用TimeUnit.MILLISECONDS.sleep()方法時,需要選擇正確的時間單位,比如:TimeUnit.SECONDS、TimeUnit.MINUTES、TimeUnit.HOURS等。
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
5. 線程睡眠不能保證精確
線程睡眠的時間并不能保證精確,它受到操作系統(tǒng)和虛擬機的干擾,可能會比預期的時間長一些,因此在實際使用中,需要考慮誤差范圍。
線程睡眠的應用場景
線程睡眠在實際應用中廣泛使用,以下是一些常見的應用場景:
1. 定時器和倒計時器
定時器和倒計時器是一種常見的實現(xiàn)方式,可以通過線程睡眠和計時器來實現(xiàn)。例如,以下代碼實現(xiàn)了一個簡單的倒計時器。
for (int i = 10; i >= 0; i--) {
System.out.println("倒計時:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2. 多線程并發(fā)控制
線程睡眠可以用來控制多個線程的并發(fā),例如通過線程睡眠,可以讓多個線程按順序執(zhí)行,而不會發(fā)生同時執(zhí)行的情況。
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
System.out.println("t1獲取obj鎖");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1釋放obj鎖");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("t2獲取obj鎖");
}
}
});
t1.start();
t2.start();
// 主線程等待子線程執(zhí)行完畢
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
3. 提高程序的運行效率
線程睡眠可以有效的控制線程的執(zhí)行時間,可以讓CPU資源分配更加均衡,提高程序的運行效率和穩(wěn)定性。例如,以下代碼使用線程睡眠優(yōu)化了圖片加載的過程。文章來源:http://www.zghlxwxcb.cn/news/detail-767482.html
long start = System.currentTimeMillis();
loadImages();
long end = System.currentTimeMillis();
System.out.println("圖片加載耗時:" + (end - start) + "ms");
private void loadImages() {
for (int i = 0; i < imageUrls.length; i++) {
loadSingleImage(imageUrls[i]);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
總結(jié):
線程睡眠作為并發(fā)編程的重要一環(huán),不僅可以有效的控制線程的執(zhí)行時間,還可以提高程序的運行效率和穩(wěn)定性,因此在實際開發(fā)中,需要合理的應用線程睡眠技術(shù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-767482.html
到了這里,關(guān)于Java——線程睡眠全方位解析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!