一、創建線程的兩種方式
在Thread類中,官方給出的創建線程有兩種方式:
-
繼承Thread類並實現run()方法;
-
實現Runnable接口,並實現run()方法。
- 當然我們也可以通過實現Callable接口,實現call()方法創建一個線程。
/**
* 創建線程的兩種方式:
* 1、繼承Thread
* 2、實現Runnable
* 3、實現Callable
*/
public class CreateThreadMain {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//創建一個通過繼承Thread類,創建一個線程
Thread extendsThreadWay = new ExtendsThreadWay();
//實現Runnable 創建一個線程
Thread implementRunnableWay =new Thread(new ImplementRunnableWay());
//實現Callable 創建一個線程,futureTask.get()獲取線程返回的結果
FutureTask<String> futureTask = new FutureTask<>(new ImplementCallableWay());
Thread implementCallableWay = new Thread(futureTask);
//啓動線程
extendsThreadWay.start();
implementCallableWay.start();
implementRunnableWay.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("通過Callable獲取的結果:futureTask.get() = " + futureTask.get());
}
}
/**
* 通過繼承Thread實現線程
*/
class ExtendsThreadWay extends Thread {
@Override
public void run() {
System.out.println("這是通過實現Thread來創建的線程。。。start");
}
}
/**
* 通過實現Callable創建線程
*/
class ImplementCallableWay implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("這是通過實現Callable來創建的線程。。。start\"");
return "result";
}
}
/**
* 通過實現runnable創建線程
*/
class ImplementRunnableWay implements Runnable{
@Override
public void run() {
System.out.println("這是通過實現Runnable來創建的線程。。。start");
}
}
運行結果如下:
二、線程的啓動
創建完一個線程可以通過start()方法啓動線程,並不能通過調用run()方法去啓動線程。因爲直接調用run()方法只是主線程中普通方法的調用,仍然使用主線程去執行的這個方法。
/**
* 調用run()和start()的區別
* 使用start()纔是真正啓動了線程
*/
public class ComparisonRunAndStart {
public static void main(String[] args) throws InterruptedException {
Thread thread = new RunAndStart();
//將當前主線程的名稱設爲main 表示使用Run方法仍然在當前主線程運行,並未開啓新的線程
Thread.currentThread().setName("main");
//將新的線程名稱設爲new thread
thread.setName("new thread");
//使用run方法
System.out.println("使用run啓動。。。。。仍然在當前線程");
thread.run();
Thread.sleep(1000);
//使用start啓動
System.out.println("使用start啓動。。。。。開啓了新的線程");
thread.start();
}
}
class RunAndStart extends Thread{
@Override
public void run() {
//打印出執行這個方法的線程名稱
System.out.println("I am :"+Thread.currentThread().getName());
}
}
運行結果如下:
三、線程的停止
- 線程的自然終止:當run()方法中的任務執行完畢或者線程拋出了異常,線程提前結束,那麼這個線程就會終止。
- 線程人爲終止:可通過stop()、suspend()、resume()方法終止、掛起和恢復線程。但是這些方法是過期的,不建議使用。因爲stop()方法會強行中斷線程,無法保證資源的正常釋放;suspend()方法使線程進入掛起狀態但是不會釋放資源,容易引發死鎖的問題;resume()與resume()方法配合使用,恢復掛起的線程。
- 線程的中斷:其他線程可以通過A線程interrupt()方法安全的中斷A線程,A線程通過檢查自身的中斷標誌位是否被置爲true來進行響應。A線程通過isInterrupted()方法或者Thread.interrupted()來判斷是否被中斷,不同的是Thread.interrupted()會將中斷表示爲置爲false。當一個線程處於阻塞狀態(sleep、join、wait等)時,如果檢查到中斷標誌位爲true會拋出InterruptedException異常,並將中斷標誌位置爲false。注意:處於死鎖狀態的線程無法被中斷。
正常中斷線程示例:
/**
* 使用interrupted中斷線程
*/
public class InterruptedThread implements Runnable {
@Override
public void run() {
//如果代碼沒被中斷,持續執行
while (!Thread.currentThread().isInterrupted()){
System.out.println("線程運行中。。");
}
}
}
class Main{
public static void main(String[] args) throws InterruptedException {
//創建一個線程
Thread thread = new Thread(new InterruptedThread());
long startMillis = System.currentTimeMillis();
//啓動線程
thread.start();
Thread.sleep(1000);
//1s後中斷線程
thread.interrupt();
System.out.println("線程被中斷,運行了:" + (System.currentTimeMillis() - startMillis) +"ms");
}
}
運行結果:
處於阻塞狀態的線程被中斷:
/**
1. 使用interrupted中斷阻塞線程
*/
public class InterruptedThread implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程運行中。。");
}
}
}
class Main{
public static void main(String[] args) throws InterruptedException {
//創建一個線程
Thread thread = new Thread(new InterruptedThread());
long startMillis = System.currentTimeMillis();
//啓動線程
System.out.println("啓動線程。。。");
thread.start();
Thread.sleep(2);
//1s後中斷線程
thread.interrupt();
System.out.println("線程被中斷,運行了:" + (System.currentTimeMillis() - startMillis) +"ms");
}
}
運行結果,拋出InterruptedException異常,並將中斷標誌位置爲false,導致線程持續運行:
四、線程的其他方法
- yield()方法:使當前線程讓出CPU執行權,進入就緒狀態,重新競爭CPU執行權,因此有可能在調用yield()方法之後,立馬又開始執行這個線程。這個操作不會釋放鎖資源。
- join()方法,在A線程中調用B線程的join()方法,會把指定的B線程加入到當前A線程中,直到B線程執行完畢繼續執行A線程。他可以使兩個並行執行的線程變爲順序執行。
/**
* 使用join()可以把指定線程加入到當前線程,使兩個線程順序運行。
* 如:在A線程中調用B線程的join方法,則開始執行B線程,直到B線程執行完畢後,繼續執行A線程。
*/
public class UseJoinMain {
public static void main(String[] args) {
Thread threadB = new ThreadB();
Thread threadA = new ThreadA(threadB);
//啓動A線程
threadA.start();
//啓動B線程
threadB.start();
}
}
/**
* 線程A
*/
class ThreadA extends Thread{
private Thread threadB;
public ThreadA(Thread threadB) {
this.threadB = threadB;
}
@Override
public void run() {
System.out.println("線程A開始執行。。。。");
try {
System.out.println("線程B加入執行。。。。線程A進入阻塞狀態");
threadB.join();
System.out.println("線程A恢復執行。。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程A執行完畢。。。。。");
}
}
/**
* 線程B
*/
class ThreadB extends Thread{
@Override
public void run() {
System.out.println("線程B開始執行。。。");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程B執行完畢。。。");
}
}
執行結果:
)
- setPriority(int n):設置線程的優先級,參數範圍爲1~10,默認優先級是5。優先級高的線程分配時間片的數量要多於優先級低的線程。設置線程優先級時,針對頻繁阻塞(休眠或者I/O操作)的線程需要設置較高優先級,而偏重計算(需要較多CPU時間或者偏運算)的線程則設置較低的優先級,確保處理器不會被獨佔。在不同的JVM以及操作系統上,線程規劃會存在差異,有些操作系統甚至會忽略對線程優先級的設定。
/**
* 優先級可設置爲1~10 優先級越高 被分配的時間片越長
* 對於偏於運算,比較耗CPU的線程可設置低的優先級 確保CPU不被獨佔
*
*
* 基本沒啥用 受操作系統與JVM影響,有的系統會忽略優先級這個設定
*/
public class PriorityThreadMain {
public static void main(String[] args) {
PriorityThread priorityThread = new PriorityThread();
priorityThread.setPriority(10);
priorityThread.start();
}
}
class PriorityThread extends Thread{
@Override
public void run() {
while (true){
System.out.println("當前線程優先級"+currentThread().getPriority());
}
}
}
4. setDaemon(true):將線程設置爲守護線程,守護線程是一種支持型線程,他主要用作程序後臺調用和支持性工作,比如垃圾回收就是一個守護線程。守護線程隨着主線程的結束而退出,守護線程中的finally塊中的內容不一定會執行。
/**
* 守護線程隨着主線程的結束而結束
* 守護線程中的finally不一定會執行
*/
public class DaemonThreadMain {
static {
Thread thread = new DaemonThread();
thread.setDaemon(true);
thread.start();
}
public static void main(String[] args) throws InterruptedException {
Thread.sleep(1000);
}
}
class DaemonThread extends Thread{
@Override
public void run() {
try {
while (true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("daemon thread is running............");
}
} finally {
System.out.println("finally .......................");
}
}
}
執行結果: