單核 cpu 下,執行緒實際還是 序列執行 的。作業系統中有一個元件叫做任務排程器,將 cpu 的時間片(windows下時間片最小約為 15 毫秒)分給不同的程式使用,只是由於 cpu 線上程間(時間片很短)的切換非常快,人類感覺是 同時執行的 。總結為一句話就是: 微觀序列,宏觀並行 。一般會將這種 執行緒輪流使用 CPU 的做法稱為並行 (concurrent)
多核 cpu下,每個 核(core) 都可以排程執行執行緒,這時候執行緒可以是並行的。
直接使用 Thread
package create;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.ThreadCre")
public class ThreadCre {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
log.debug("running");
}
};
t.start();
log.debug("running");
}
}
使用 Runnable 配合 Thread
package create;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.RunnableCre")
public class RunnableCre {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
log.debug("running");
}
};
Thread t = new Thread(r,"t2");
t.start();
}
}
使用 lambda 方式簡化
package create;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.RunnableCre")
public class RunnableCre {
public static void main(String[] args) {
Runnable r = () -> { log.debug("running"); };
Thread t = new Thread(r,"t2");
t.start();
}
}
FutureTask 配合 Thread
package create;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
@Slf4j(topic = "c.FutureTaskCre")
public class FutureTaskCre {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("running...");
Thread.sleep(1000);
return 100;
}
});
Thread t = new Thread(task,"t1");
t.start();
log.debug("{}",task.get());
}
}
每個執行緒啟動後,虛擬機器器就會為其分配一塊棧記憶體。
因為以下一些原因導致 cpu 不再執行當前的執行緒,轉而執行另一個執行緒的程式碼
當 Context Switch 發生時,需要由作業系統儲存當前執行緒的狀態,並恢復另一個執行緒的狀態,Java 中對應的概念就是程式計數器(Program Counter Register),它的作用是記住下一條 jvm 指令的執行地址,是執行緒私有的
方法名 | static | 功能說明 | 注意 |
---|---|---|---|
start() | 啟動一個新執行緒,在新的執行緒執行 run 方法中的程式碼 | start 方法只是讓執行緒進入就緒,裡面的程式碼不一定立刻執行(CPU的時間片還沒有分給它)。每個執行緒物件的 start 方法只能呼叫一次,否則會出現異常 | |
run() | 新執行緒啟動後會呼叫的方法 | 如果在構造 Thread 物件時傳遞了 Runnable 引數,則執行緒啟動後會呼叫 Runnable 中的 run 方法。但可以建立 Thread 的子類物件來覆蓋預設行為 | |
join() | 等待執行緒執行結束 | ||
join(long n) | 等待執行緒執行結果,最多等待 n 毫秒 | ||
getId() | 獲取執行緒長整型的 id | ||
getName() | 獲取執行緒名 | ||
setName(String) | 修改執行緒名 | ||
getPriority() | 獲取執行緒優先順序 | ||
setPriority(int) | 修改執行緒優先順序 | java中規定執行緒優先順序是1~10 的整數,較大的優先順序能提高該執行緒被 CPU 排程的機率 | |
getState() | 獲取執行緒狀態 | Java 中執行緒狀態是用 6 個 enum 表示,分別為:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED | |
isInterrupted() | 判斷是否被打斷 | 不會清除 打斷標記 | |
isAlive() | 執行緒是否存活(還沒有執行完畢) | ||
interrupt() | 打斷執行緒 | 如果被打斷執行緒正在 sleep,wait,join 會導致被打斷的執行緒丟擲 InterruptedException,並清除 打斷標記 ;如果打斷的正在執行的執行緒,則會設定 打斷標記 ;park 的執行緒被打斷,也會設定 打斷標記 | |
interrupted() | static | 判斷當前執行緒是否被打斷 | 會清除 打斷標記 |
currentThread() | static | 獲取當前正在執行的執行緒 | |
sleep(long n) | static | 讓當前執行的執行緒休眠 n 毫秒,休眠時讓出 CPU 的時間片給其他程式 | |
yield() | static | 提示執行緒排程器讓出當前執行緒對CPU的使用 | 主要是為了測試和偵錯 |
呼叫 run
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.run();
log.debug("do other things ...");
}
輸出
19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...
程式仍在 main 執行緒執行, FileReader.read() 方法呼叫還是同步的
總結
sleep
yield
執行緒優先順序
等待一個執行緒執行結束
等待多個執行緒的結果
情況一:
package testJoin;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.demo1")
public class demo1 {
static int r = 0 , r1 = 0 , r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
r1 = 10;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
r1 = 20;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
log.debug("join begin");
t1.join();
log.debug("t1 join end");
t2.join();
log.debug("t2 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}",r1,r2,end-start);
}
}
輸出:
14:18:02 [main] c.demo1 - join begin
14:18:03 [main] c.demo1 - t1 join end
14:18:04 [main] c.demo1 - t2 join end
14:18:04 [main] c.demo1 - r1: 20 r2: 0 cost: 2008
情況二:
package testJoin;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.demo1")
public class demo1 {
static int r = 0 , r1 = 0 , r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
r1 = 10;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
r1 = 20;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
log.debug("join begin");
t2.join();
log.debug("t2 join end");
t1.join();
log.debug("t1 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}",r1,r2,end-start);
}
}
輸出:
14:19:19 [main] c.demo1 - join begin
14:19:21 [main] c.demo1 - t2 join end
14:19:21 [main] c.demo1 - t1 join end
14:19:21 [main] c.demo1 - r1: 20 r2: 0 cost: 2006
另外 join 也可以帶引數,是有時效的等待。當到設定時間執行緒還未給出結果,直接向下執行,不再等待。如果設定時間還沒到但是執行緒已經執行完畢,則直接向下執行,不再等待。
打斷 sleep,wait,join 的執行緒
這幾個方法都會讓執行緒進入阻塞狀態
打斷 sleep 的執行緒, 會清空打斷狀態,以 sleep 為例
package testInterrupt;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.demo1")
public class demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("sleep...");
try {
Thread.sleep(5000);
//注意:sleep,wait,join等被打斷並以異常形式表現出來後
// 會把打斷標記重新置為 false(未打斷狀態)
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
log.debug("打斷標記:{}",t1.isInterrupted());
}
}
輸出:
15:08:12 [t1] c.demo1 - sleep...
15:08:13 [main] c.demo1 - interrupt
15:08:13 [main] c.demo1 - 打斷標記:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at testInterrupt.demo1.lambda$main$0(demo1.java:11)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
打斷正常執行的執行緒打斷標記置為:true
package testInterrupt;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.demo2")
public class demo2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true){
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted){
log.debug("被打斷了,退出迴圈");
break;
}
}
},"t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
}
輸出:
15:17:40 [main] c.demo2 - interrupt
15:17:40 [t1] c.demo2 - 被打斷了,退出迴圈
打斷 park 執行緒
package testInterrupt;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.demo4")
public class demo4 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打斷狀態:{}",Thread.currentThread().isInterrupted());
},"t1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
輸出:
14:16:21 [t1] c.demo4 - park...
14:16:22 [t1] c.demo4 - unpark...
14:16:22 [t1] c.demo4 - 打斷狀態:true
package testInterrupt;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.demo3")
public class demo3 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination tpt = new TwoPhaseTermination();
tpt.start();
Thread.sleep(3500);
tpt.stop();
}
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination{
private Thread monitor;
//啟動監控執行緒
public void start(){
monitor = new Thread(() -> {
while (true){
Thread current = Thread.currentThread();
if(current.isInterrupted()){
log.debug("料理後事");
break;
}
try {
Thread.sleep(1000);//情況1
log.debug("執行監控記錄");//情況2
} catch (InterruptedException e) {
e.printStackTrace();
//重新設定打斷標記
current.interrupt();
}
}
});
monitor.start();
}
//終止監控執行緒
public void stop(){
monitor.interrupt();
}
}
輸出:
15:33:02 [Thread-0] c.TwoPhaseTermination - 執行監控記錄
15:33:03 [Thread-0] c.TwoPhaseTermination - 執行監控記錄
15:33:04 [Thread-0] c.TwoPhaseTermination - 執行監控記錄
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at testInterrupt.TwoPhaseTermination.lambda$start$0(demo3.java:29)
at java.lang.Thread.run(Thread.java:748)
15:33:04 [Thread-0] c.TwoPhaseTermination - 料理後事
Process finished with exit code 0
還有一些不推薦使用的方法,這些方法已過時,容易破壞同步程式碼塊,造成執行緒死鎖
方法名 | static | 功能說明 |
---|---|---|
stop() | 停止執行緒執行 | |
suspend() | 掛起(暫停)執行緒執行 | |
resume() | 恢復執行緒執行 |