在Java多線程編程中,Thread類是其中一個核心和關鍵的角色。因此,對該類中一些基礎常用方法的理解和熟練使用是開發多線程代碼的基礎。本篇主要總結一下Thread中常用的一些靜態方法的含義及代碼中的使用。
sleep方法
源碼如下:
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
*
* @param millis
* the length of time to sleep in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
可以看到sleep是一個靜態的本地方法,因為是本地方法,所以並沒有java代碼的實現,其實是調用了底層的C庫函數來實現的睡眠。
有一個long類型的參數,表示睡眠多少毫秒。
閱讀注釋,sleep方法的含義就是,讓當前正在執行任務的線程睡眠(臨時地停止執行)指定的毫秒數,這個精度和准確性是用系統時鍾和調度器保證的。但是,線程並不會釋放它擁有的鎖。
注意該方法會拋出InterruptedException中斷異常。
sleep不會釋放鎖代碼示例:
public class ThreadsleepDemo{
private Object object = new Object();
public static void main(String[] args) {
ThreadsleepDemo threadsleepDemo = new ThreadsleepDemo();
Thread thread1 = threadsleepDemo.new SleepDemoThread();
thread1.setName("線程1");
Thread thread2 = threadsleepDemo.new SleepDemoThread();
thread2.setName("線程2");
thread1.start();
thread2.start();
}
class SleepDemoThread extends Thread{
@Override
public void run() {
synchronized (object){
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
}
輸出結果如下:
線程1開始運行
線程1運行結束
線程2開始運行
線程2運行結束
可以多運行幾次,可能會有線程1在上面或和線程2在上面,但始終都是一個行程運行完了才會運行另一個線程,中間不會插入進來一個線程運行。
yield方法
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
public static native void yield();
當前線程對調度器的一個暗示,表示願意讓出CPU執行器的當前使用權,但是調度器可以自由忽略這個提示。
Yeild是一種在可能會過度使用一個CPU的多個線程之間提升相對進度試探性嘗試。它的使用應該結合詳細的性能分析和基准測試來進行,確保它確實有預期的效果。
很少使用這種方法。 它可能對調試或測試有用,可能有助於根據競態條件重現錯誤。 在設計並發控制結構(例如java.util.concurrent.locks包中的並行控制結構)時也可能有用。
join方法
join有三個重載的方法
join()
join(long millis)
join(long millis,int nanoseconds)
主要看下第二個方法的源碼
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*/
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方法其實就是調用了join(0),即永遠等待下去。不過通過源碼我們可以看到,在while循環中有一個條件判斷,即isAlive()方法,意思是如果當前線程還活着,就會一直等待下去。
有點懵,看個例子應該加深下理解。比如睡前想刷個抖音。
刷抖音的工作我們交給一個線程來完成。
public class ScanDouyin extends Thread{
// 瀏覽抖音的時長
private int scanTime;
public ScanDouyin(String name, int scanTime){
super(name);
scanTime = this.scanTime;
}
@Override
public void run() {
System.out.println(getName() + ":開始刷抖音了");
try {
// 刷抖音的時間
sleep(scanTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() +":抖音刷完了,睡覺吧");
}
}
下面是准備睡覺的線程
/**
* 准備睡覺了,睡前想要刷個抖音
*/
public class ReadySleep extends Thread{
private ScanDouyin scanDouyin;
public ReadySleep(String name,ScanDouyin scanDouyin){
super(name);
this.scanDouyin = scanDouyin;
}
@Override
public void run() {
System.out.println(getName() + ":准備開始睡覺啦");
try {
// 睡前刷把抖音
scanDouyin.join();
// 准備睡覺的具體內容
System.out.println("開始睡覺");
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":zzzzzzzz,已經睡着了");
}
public static void main(String[] args) {
ScanDouyin scanDouyin = new ScanDouyin("刷抖音線程",10000);
ReadySleep readySleep = new ReadySleep("睡覺線程",scanDouyin);
readySleep.start();
scanDouyin.start();
}
}
輸出結果如下:
睡覺線程:准備開始睡覺啦
刷抖音線程:開始刷抖音了
刷抖音線程:抖音刷完了,睡覺吧
開始睡覺
睡覺線程:zzzzzzzz,已經睡着了
這里我們我設置的刷抖音的時間是10s,睡覺線程的執行時間是100ms,也就是0.1s。
可以看到因為在睡覺線程中調用了刷抖音線程的join方法,使得睡覺的線程必須等待直到刷完抖音(刷抖音線程執行完畢,線程消亡),才能開始睡覺。
至此,應該可以明白,如果某個線程在另一個線程t上調用t.join(),此線程將被掛起,直到目標線程t結束才恢復(即t.isAlive()方法返回假)。