帶你聊聊 Java 并發(fā)編程之線程基礎(chǔ)
01、簡(jiǎn)介
百丈高樓平地起,要想學(xué)好多線程,首先還是的了解一下線程的基礎(chǔ),這邊文章將帶著大家來(lái)了解一下線程的基礎(chǔ)知識(shí)。
02、線程的創(chuàng)建方式
- 實(shí)現(xiàn) Runnable 接口
- 繼承 Thread 類
- 實(shí)現(xiàn) Callable 接口通過(guò) FutureTask 包裝器來(lái)創(chuàng)建線程
- 通過(guò)線程池創(chuàng)建線程
下面將用線程池和 Callable 的方式來(lái)創(chuàng)建線程
- public class CallableDemo implements Callable<String> {
- @Override
- public String call() throws Exception {
- int a=1;
- int b=2;
- System. out .println(a+b);
- return "執(zhí)行結(jié)果:"+(a+b);
- }
- public static void main(String[] args) throws ExecutionException, InterruptedException {
- //創(chuàng)建一個(gè)可重用固定線程數(shù)為1的線程池
- ExecutorService executorService = Executors.newFixedThreadPool (1);
- CallableDemo callableDemo=new CallableDemo();
- //執(zhí)行線程,用future來(lái)接收線程的返回值
- Future<String> future = executorService.submit(callableDemo);
- //打印線程的返回值
- System. out .println(future.get());
- executorService.shutdown();
- }
- }
執(zhí)行結(jié)果
- 3
- 執(zhí)行結(jié)果:3
03、線程的生命周期
- NEW:初始狀態(tài),線程被構(gòu)建,但是還沒(méi)有調(diào)用 start 方法。
- RUNNABLED:運(yùn)行狀態(tài),JAVA 線程把操作系統(tǒng)中的就緒和運(yùn)行兩種狀態(tài)統(tǒng)一稱為“運(yùn)行中”。調(diào)用線程的 start() 方法使線程進(jìn)入就緒狀態(tài)。
- BLOCKED:阻塞狀態(tài),表示線程進(jìn)入等待狀態(tài),也就是線程因?yàn)槟撤N原因放棄了 CPU 使用權(quán)。比如訪問(wèn) synchronized 關(guān)鍵字修飾的方法,沒(méi)有獲得對(duì)象鎖。
- Waiting :等待狀態(tài),比如調(diào)用 wait() 方法。
- TIME_WAITING:超時(shí)等待狀態(tài),超時(shí)以后自動(dòng)返回。比如調(diào)用 sleep(long millis) 方法
- TERMINATED:終止?fàn)顟B(tài),表示當(dāng)前線程執(zhí)行完畢。
看下源碼:
- public enum State {
- NEW,
- RUNNABLE,
- BLOCKED,
- WAITING,
- TIMED_WAITING,
- TERMINATED;
- }
04、線程的優(yōu)先級(jí)
- 線程的最小優(yōu)先級(jí):1
- 線程的最大優(yōu)先級(jí):10
- 線程的默認(rèn)優(yōu)先級(jí):5
- 通過(guò)調(diào)用 getPriority() 和 setPriority(int newPriority) 方法來(lái)獲得和設(shè)置線程的優(yōu)先級(jí)
看下源碼:
- /**
- * The minimum priority that a thread can have.
- */
- public final static int MIN_PRIORITY = 1;
- /**
- * The default priority that is assigned to a thread.
- */
- public final static int NORM_PRIORITY = 5;
- /**
- * The maximum priority that a thread can have.
- */
- public final static int MAX_PRIORITY = 10;
看下代碼:
- public class ThreadA extends Thread {
- public static void main(String[] args) {
- ThreadA a = new ThreadA();
- System.out.println(a.getPriority());//5
- a.setPriority(8);
- System.out.println(a.getPriority());//8
- }
- }
線程優(yōu)先級(jí)特性:
- 繼承性:比如 A 線程啟動(dòng) B 線程,則B線程的優(yōu)先級(jí)與 A 是一樣的。
- 規(guī)則性:高優(yōu)先級(jí)的線程總是大部分先執(zhí)行完,但不代表高優(yōu)先級(jí)線程全部先執(zhí)行完。
- 隨機(jī)性:優(yōu)先級(jí)較高的線程不一定每一次都先執(zhí)行完。
05、線程的停止
- stop() 方法,這個(gè)方法已經(jīng)標(biāo)記為過(guò)時(shí)了,強(qiáng)制停止線程,相當(dāng)于 kill -9。
- interrupt() 方法,優(yōu)雅的停止線程。告訴線程可以停止了,至于線程什么時(shí)候停止,取決于線程自身。
看下停止線程的代碼:
- public class InterruptDemo {
- private static int i ;
- public static void main(String[] args) throws InterruptedException {
- Thread thread = new Thread(() -> {
- //默認(rèn)情況下isInterrupted 返回 false、通過(guò) thread.interrupt 變成了 true
- while (!Thread.currentThread().isInterrupted()) {
- i++;
- }
- System.out.println("Num:" + i);
- }, "interruptDemo");
- thread.start();
- TimeUnit.SECONDS.sleep(1);
- thread.interrupt(); //不加這句,thread線程不會(huì)停止
- }
- }
看上面這段代碼,主線程 main 方法調(diào)用 thread線程的 interrupt() 方法,就是告訴 thread 線程,你可以停止了(其實(shí)是將 thread 線程的一個(gè)屬性設(shè)置為了 true ),然后 thread 線程通過(guò) isInterrupted() 方法獲取這個(gè)屬性來(lái)判斷是否設(shè)置為了 true。這里我再舉一個(gè)例子來(lái)說(shuō)明一下,
看代碼:
- public class ThreadDemo {
- private volatile static Boolean interrupt = false ;
- private static int i ;
- public static void main(String[] args) throws InterruptedException {
- Thread thread = new Thread(() -> {
- while (!interrupt) {
- i++;
- }
- System.out.println("Num:" + i);
- }, "ThreadDemo");
- thread.start();
- TimeUnit.SECONDS.sleep(1);
- interrupt = true;
- }
- }
是不是很相似,再簡(jiǎn)單總結(jié)一下:
當(dāng)其他線程通過(guò)調(diào)用當(dāng)前線程的 interrupt 方法,表示向當(dāng)前線程打個(gè)招呼,告訴他可以中斷線程的執(zhí)行了,并不會(huì)立即中斷線程,至于什么時(shí)候中斷,取決于當(dāng)前線程自己。
線程通過(guò)檢查自身是否被中斷來(lái)進(jìn)行相應(yīng),可以通過(guò) isInterrupted() 來(lái)判斷是否被中斷。
這種通過(guò)標(biāo)識(shí)符來(lái)實(shí)現(xiàn)中斷操作的方式能夠使線程在終止時(shí)有機(jī)會(huì)去清理資源,而不是武斷地將線程停止,因此這種終止線程的做法顯得更加安全和優(yōu)雅。
06、線程的復(fù)位
兩種復(fù)位方式:
- Thread.interrupted()
- 通過(guò)拋出 InterruptedException 的方式
然后了解一下什么是復(fù)位:
線程運(yùn)行狀態(tài)時(shí) Thread.isInterrupted() 返回的線程狀態(tài)是 false,然后調(diào)用 thread.interrupt() 中斷線程 Thread.isInterrupted() 返回的線程狀態(tài)是 true,最后調(diào)用 Thread.interrupted() 復(fù)位線程Thread.isInterrupted() 返回的線程狀態(tài)是 false 或者拋出 InterruptedException 異常之前,線程會(huì)將狀態(tài)設(shè)為 false。
下面來(lái)看下兩種方式復(fù)位線程的代碼,首先是 Thread.interrupted() 的方式復(fù)位代碼:
- public class InterruptDemo {
- public static void main(String[] args) throws InterruptedException {
- Thread thread = new Thread(() -> {
- while (true) {
- //Thread.currentThread().isInterrupted()默認(rèn)是false,當(dāng)main方式執(zhí)行thread.interrupt()時(shí),狀態(tài)改為true
- if (Thread.currentThread().isInterrupted()) {
- System.out.println("before:" + Thread.currentThread().isInterrupted());//before:true
- Thread.interrupted(); // 對(duì)線程進(jìn)行復(fù)位,由 true 變成 false
- System.out.println("after:" + Thread.currentThread().isInterrupted());//after:false
- }
- }
- }, "interruptDemo");
- thread.start();
- TimeUnit.SECONDS.sleep(1);
- thread.interrupt();
- }
- }
拋出 InterruptedException 復(fù)位線程代碼:
- public class InterruptedExceptionDemo {
- public static void main(String[] args) throws InterruptedException {
- Thread thread = new Thread(() -> {
- while (!Thread.currentThread().isInterrupted()) {
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- // break;
- }
- }
- }, "interruptDemo");
- thread.start();
- TimeUnit.SECONDS.sleep(1);
- thread.interrupt();
- System.out.println(thread.isInterrupted());
- }
- }
結(jié)果:
- false
- java.lang.InterruptedException: sleep interrupted
- at java.lang.Thread.sleep(Native Method)
- at java.lang.Thread.sleep(Thread.java:340)
- at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
- at com.cl.concurrentprogram.InterruptedExceptionDemo.lambda$main$0(InterruptedExceptionDemo.java:16)
- at java.lang.Thread.run(Thread.java:748)
需要注意的是,InterruptedException 異常的拋出并不意味著線程必須終止,而是提醒當(dāng)前線程有中斷的操作發(fā)生,至于接下來(lái)怎么處理取決于線程本身,比如
- 直接捕獲異常不做任何處理
- 將異常往外拋出
- 停止當(dāng)前線程,并打印異常信息
像我上面的例子,如果拋出 InterruptedException 異常,我就break跳出循環(huán)讓 thread 線程終止。
為什么要復(fù)位:
Thread.interrupted() 是屬于當(dāng)前線程的,是當(dāng)前線程對(duì)外界中斷信號(hào)的一個(gè)響應(yīng),表示自己已經(jīng)得到了中斷信號(hào),但不會(huì)立刻中斷自己,具體什么時(shí)候中斷由自己決定,讓外界知道在自身中斷前,他的中斷狀態(tài)仍然是 false,這就是復(fù)位的原因。