Thread和Runnable 的區別在面試當中是比較容易考的,自己又剛好在做這個的實驗課題,於是乎,決定好好的學習這一部分知識,並把我查過的資料,通過自己的理解,給大家整理出來。
目錄
面向小白學習法——Thread和Runnable,擴展Callable!
Thread和Runnable的區別 (Callable擴展)
面向小白學習法——Thread和Runnable,擴展Callable!
Thread和Runnable的區別 (Callable擴展)
在面試當中,面試官特別喜歡問:“開啓線程的兩種方式,Thread 和 Runnable有什麼區別?”。然後很多人可能看了許多博客,大致的內容是這樣的(Callable不常用,但我依然爲大家擴展了出來)——
Java實現多線程的方法有三種,分別是繼承thread類,實現Runnable接口,實現實現callable接口(call()方法有返回值,run()方法無返回值)。接下來對它們進行比較——
Thread:
- 繼承Thread類;
- 重寫run方法,並且沒有返回值(run()方法無返回值);
- 每次new Thread都是獨立的,資源不共享;
Runnable:
- 實現Runnable接口;
- 重寫run方法並且沒有返回值(run()方法無返回值);
- 資源共享,比Thread類更加靈活,無單繼承的限制;
Callable:
- 實現Callable接口;
- 使用call()方法,並且有返回值(call()方法有返回值);
- 可以拋出受檢查的異常,比如ClassNotFoundException(call()方法可以);
如果想回答是上面內容的話,那麼恭喜你,你其實並不太瞭解Runnable和Thread!看完這篇文章,你或許就明白了。
線程
線程的引入:
線程,在網絡或多用戶環境下,一個服務器通常需要接收大量且不確定數量用戶的併發請求,爲每一個請求都創建一個進程顯然是行不通的,——無論是從系統資源開銷方面或是響應用戶請求的效率方面來看。因此,操作系統中線程的概念便被引進了。
使用的範圍:
1.服務器中的文件管理或通信控制
2.前後臺處理
3.異步處理
多線程
-
線程是一個進程中的執行場景。一個進程可以啓動多個線程
-
多線程作用不是爲了提高執行速度,而是提高應用程序的使用率
-
線程和線程共享“堆內存和方法區內存”,棧內存是獨立的,一個線程一個棧
-
由於多線程在來回切換,所以給現實世界中的人類一種錯覺:感覺多個線程在同時併發執行。
Thread
構造一個thread類:
package my;
import java.util.Date;
public class myThread01 extends Thread
{
@Override
public void run()
{
System.out.println("創建一個線程" + new Date());
}
}
thread類的常用方法
String getName() | 返回該線程的名字 |
void setName(String name) |
改變線程名字,使之與參數name相同 |
int getPriority() | 返回線程優先級 |
void setPriority(int newPriority) | 更改線程優先級 |
boolean isDaemon() | 測試該線程是否爲守護線程 |
void setDaemon(boolean on) | 將該線程標記爲守護線程或用戶線程 |
static void sleep(long millis) | |
void interrupt() | 中斷線程 |
static void yield() | 暫停當前正在執行的線程對象,並執行其他線程 |
void join() | 等待該線程終止 |
void run() | |
void start() |
sleep()方法:
Thread.sleep(times) 使當前線程從running狀態放棄處理器進入Block狀態,休眠times毫秒,再返回Runnable狀態。
public class Test
{
public static void main(String[] args) throws InterruptedException {
Thread t1 = new myThread01();//創建線程
Thread t2 = new myThread01();
t1.start();//告訴JVM再分配一個新的棧給t線程,run不需程序員手動調用
Thread.sleep(5000);
t2.start();//系統線程啓動後自動調用run方法
}
}
它的運行結果是:
創建第一個線程後,休眠5秒,再創建另一個線程,所以時間相隔5秒。
注意:當一個線程處於睡眠阻塞時,若被其他線程調用,interrupt方法中斷,則sleep方法會拋出InterruptedException異常
public static void main(String[] args) {
final Thread thread1 = new Thread() {
public void run() {
System.out.println("1號線程準備進入睡眠狀態...");
try {
Thread.sleep(1000000);
}catch(InterruptedException e) {
System.out.println("1號線程被interrupt方法中斷了...");
}
}
};//注意分號
Thread thread2 = new Thread() {
public void run() {
System.out.println("2號線程場了!");
System.out.println("2號線程Running...");
try{
Thread.sleep(1000);
}catch(InterruptedException e) {
}
thread1.interrupt();
}
};//注意分號
thread1.start();
thread2.start();
}
結果:
注意:當一個方法中的局部內部類中需要引用該方法的其他局部變量,這個變量必須使用final。
後臺線程:
也被稱作:守護線程、精靈線程。
特點:用法與前臺線程差不多,只是當一個進程的所有前臺進程都結束後,後臺進程將會被強制結束(正在運行中的也會結束),從而使得進程結束程序。
package my;
public class Test
{
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
System.out.println("前臺線程運行");
try {
Thread.sleep(4000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("結束進程");
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 10; i ++) {
System.out.println(i + ":running");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
};
t2.setDaemon(true);//t2設置爲後臺進程
t1.start();
t2.start();
}
}
結果:
setPriority()線程優先級
優先級被劃分爲1-10,1最低10最高。優先級越高的線程被分配時間片的機會越多,那麼被CPU執行的機會越高。
public static void main(String[] args) {
//設置最高優先級的線程
Thread t1 = new Thread() {
public void run() {
System.out.println("最低優先級線程t1");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("結束進程t1");
}
};
//設置較高優先級的線程
Thread t2 = new Thread() {
public void run() {
System.out.println("最高優先級線程t2");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("結束進程t2");
}
};
//設置最低優先級線程
Thread t3 = new Thread() {
public void run() {
System.out.println("較高優先級線程t3");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("結束進程t3");
}
};
t1.setPriority(1);
t2.setPriority(10);
t3.setPriority(6);
t1.start();
t2.start();
t3.start();
}
運行兩次的結果
注意:兩次運行結果不一樣,是沒有問題的。因爲設置了優先級也不能100%控制線程調度。只是最大程度的告知線程調度以更多的機率分配時間片給優先級高的線程。
join()方法
此方法允許執行這個方法的線程在該方法所屬線程後等待,直到該方法所屬線程結束後方可繼續運行,否則會一致處於 阻塞狀態。
//執行兩個命令,只有第一個命令執行完成,才能執行第二個命令。
//即第二個命令等待第一個命令執行完成,再執行...
private static boolean isFinish = false;//判斷命令是否結束
public static void main(String[] args) {
final Thread download = new Thread() {
public void run() {
System.out.println("執行第一個命令:running...");
for(int i = 1; i <= 10; i++) {
System.out.println("running:" + i +"!");
try {
Thread.sleep(50);
}catch(InterruptedException e) {
}
}
System.out.println("完成該命令,停止!");
isFinish = true;
}
};
Thread show = new Thread() {
public void run() {
try {
System.out.println(">>>等待第一個命令結束...");
download.join();
System.out.println(">>>第一個命令結束,執行第二個命令...");
if(!isFinish) {
throw new RuntimeException("Error!命令執行錯誤!");
}
System.out.println(">>>執行第二個命令!");
}catch(InterruptedException e) {
}
}
};
download.start();
show.start();
}
結果:
同步運行:執行有先後順序
異步運行:執行沒有先後順序,多線程就是異步運行的。
join()可以協調線程之間的同步運行。
yield()方法
放棄當前的CPU資源,將它讓給其它任務。但放棄的時間不確定。
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run()
{
System.out.println("2號線程:線程1,命令5完成後等等我!");
System.out.println("1好線程:好的,我現在出發了...");
for(int i = 0; i < 10; i++) {
System.out.println("1號線程執行中..." + i);
if(i == 5) {
System.out.println("完成命令5,等待2號線程執行!");
this.yield();
}
}
System.out.println("1號線程執行結束。");
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("2號線程執行中..." + i);
}
System.out.println("2號線程:已執行結束,1號線程你繼續。");
}
};
t1.start();
t2.start();
}
結果:
每次執行可能是不一樣的!
使用yield方法時,該線程就會把CPU時間讓掉,讓其他或者自己的線程執行(也就是誰先搶到誰執行)。但是吧,其實有沒有此方法對上述代碼片段沒有明顯的區別。
Runnable
在使用Thread的時候只需要new一個實例出來,調用百start()方法即可以啓度動一個線程。
Thread Test = new Thread();
Test.start();
在使用Runnable的時候需要先new一個繼承Runnable的實例,之後用子類Thread調用。
Test impelements Runnable
Test t = new Test();
Thread test = new Thread(t);
class MyThread implements Runnable
{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run()
{
for(int i = 0; i < 100; i++) {
System.out.println("線程開始:" + this.name+",i ="+ i);
}
}
};
main:
public class Test
{
public static void main(String[] args) {
MyThread t1 = new MyThread("t1");
t1.run();
}
}
結果:
特別注意:
Java中真正能創建線程的只有Thread類對象
通過實現Runnable的方式,最終還是通過Thread類對象來創建線程
所以有些人把實現了Runnable接口的類,稱爲線程輔助類,而Thread類纔是真正的線程類
面試官問“Thread和Runnable的區別?”這個問題中的所謂的用Runnable的形式,其實只不過是用Runnable調用Thread的構造方法,其實創建的還是Thread對象。
Runnable 只是一個接口定義,表示一個可執行的代碼單元,Thread類只是實現了Runnable接口,並且Thread類有一個構造方法中是接收一個Runnable的接口的。
擴展——Callable
Callable可以返回執行結果,是個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果;
Callable接口的call()方法允許拋出異常;Runnable的run()方法異常只能在內部消化,不能往上繼續拋。
class CallableThreadTest implements Callable<Integer>
{
public static void main(String[] args) throws ExecutionException,InterruptedException{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
new Thread(ft,"有返回值的線程").start();
System.out.println("子線程的返回值" + ft.get());
}
@Override
public Integer call() throws Exception
{
int i;
for( i = 0; i < 10; i += 1) {
System.out.println(Thread.currentThread().getName() + "" + i);
}
return i;
}
}
結果:
注:Callalble接口支持返回執行結果,需要調用FutureTask.get()得到,此方法會阻塞主進程的繼續往下執行,如果不調用不會阻塞。
Future:表示一個可能還沒有完成的異步任務的結果,針對這個結果可以添加Callback以便在任務執行成功或失敗後作出相應的操作。
FutureTask:一個可取消的異步計算。FutureTask提供了對Future的基本實現,可以調用方法去開始和取消一個計算,可以查詢計算是否完成並且獲取計算結果。
結語:我是劉小白,喜歡和大家一起分享,一起學習,共同進步。
關注博主鏈接 博主的個人主頁