一、線程的概述
進程:正在運行的程序,負責了這個程序的內存空間分配,代表了內存中的執行區域。
線程:就是在一個進程中負責一個執行路徑。
多線程:就是在一個進程中多個執行路徑同時執行。
1.1多線程的好處:
- 解決了一個進程里面可以同時運行多個任務(執行路徑)。
- 提供資源的利用率,而不是提供效率。
1.2多線程的弊端:
- 降低了一個進程里面的線程的執行頻率。
- 對線程進行管理要求額外的 CPU開銷。線程的使用會給系統帶來上下文切換的額外負擔。
- 公有變量的同時讀或寫。當多個線程需要對公有變量進行寫操作時,后一個線程往往會修改掉前一個線程存放的數據,發生線程安全問題。
- 線程的死鎖。即較長時間的等待或資源競爭以及死鎖等多線程癥狀。
二、創建線程
2.1 創建線程的方式一(繼承)
通過繼承Thread
來創建線程。步驟:
- 自定義一個類繼承
Thread
. - 重寫
Thread
的run
方法。 - 創建
Thread
子類的對象,然后調用start
方法開啟一個線程。
注意:千萬不要直接調用run方法,直接調用run方法就相當于調用了一個普通的方法而已,并沒有開啟一個新的線程。
public class Demo1 extends Thread {
//自定義線程的任務代碼寫到run方法中。
@Override
public void run() {
for(int i = 0 ; i<100 ; i++){
System.out.println("自定義線程:"+ i);
}
}
public static void main(String[] args) throws Exception {
//主線程
Demo1 d = new Demo1();
d.start();//開啟了一個新的線程,一個線程開啟的時候就會執行run方法中的代碼
for(int i = 0 ; i<100 ; i++){
System.out.println("主線程:"+ i);
}
}
}
2.2 為什么要重寫run()
方法?
每個線程都有自己的任務代碼,主線程的任務代碼是在main方法中,因為每個線程都有自己要執行的代碼,自定義線程要執行的代碼就放在run方法 中。重寫run方法的目的就是把自定義線程的任務代碼編寫到run方法中。
2.3 線程的使用細節
- 線程的啟動使用父類的
start()
方法 - 如果線程對象直接調用
run()
,那么JVM
不會把它當作線程來運行,會認為是普通的方法調用 - 線程的啟動只能有一次,否則拋出異常
- 可以直接創建
Thread
類的對象并啟動該線程,但是如果沒有重寫run()
,什么也不執行 - 可以使用匿名內部類的實現方式來啟動一個線程
2.4 線程的多種狀態

線程狀態之間的轉換
- 創建:新創建了一個線程對象。
- 可運行:線程對象創建后,其線程調用了該對象的
start()
方法。該狀態的線程位于可運行線程池中,變得可運行,等待獲取cpu
的執行權。 - 運行:就緒狀態的線程獲取了
CPU
執行權,執行程序代碼。 - 阻臨時塞: 阻塞狀態是線程因為某種原因放棄
CPU
使用權,例如調用了sleep
或者wait
方法,線程會暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態 - 死亡:線程執行完它的任務時
2.5 線程的常用方法
- ·
Thread(String name)
初始化線程的名字 -
getName()
返回線程的名字 -
setName(String name)
設置線程對象名 -
sleep()
靜態的方法,線程睡眠指定的毫秒數。使用該方法需要拋出異常,并不會釋放鎖對象 -
getPriority()
返回當前線程對象的優先級,默認線程的優先級是5 -
setPriority(int newPriority)
設置線程的優先級,雖然設置了線程的優先級,但是具體的實現取決于底層的操作系統的實現(最大的優先級是10 ,最小的1 , 默認是5) -
currentThread()
返回CPU正在執行的線程的對象 -
getId()
返回該線程的標識符。線程 ID 是一個正的long
型數值,在創建該線程時生成。線程ID是唯一的,并終生不變。線程終止時,該線程 ID 可以被重新使用。
2.6 創建線程的方式二(實現Runnable接口)
創建線程的第二種方式:實現Runnable接口。該類中的代碼就是對線程要執行的任務的定義。步驟如下:
- 定義一個實現
Runnable
接口的類 - 實現
Runnable
接口中的run
方法,就是將線程運行的代碼放入在run
方法中 - 通過
Thread
類建立線程對象 - 將
Runnable
接口的子類對象作為實際參數,傳遞給Thread
類構造方法 - 調用
Thread
類的start
方法開啟線程,線程最終會調用Runable
接口子類的run
方法
class RunableDemo1 {
public static void main(String[] args) {
//1.創建Runnable的子類對象
MyRun mr = new MyRun();
//2.通過Runnbale的子類對象創建線程對象
Thread t1 = new Thread(mr);
//3.啟動線程
t1.start();
for (int i = 0; i < 200; i++) {
System.out.println("main:"+i);
}
}
}
class MyRun implements Runnable{ //建立Runnable接口的實現類
@Override
public void run() {
for(int i=0;i<200;i++){
System.out.println("MyRun"+i);
}
}
}
【提示】推薦使用第二種方式,即實現Runnable
接口的方式。因為java
是單繼承的。
2.7 為什么要將Runnable接口的子類對象傳遞給Thread的構造函數?
因為自定義的run
方法所屬對象是Runnable
接口的子類對象,所以要讓線程去執行指定對象的run
方法。在創建Thread
線程對象的時候,會調用線程的run
方法,而調用線程run
方法的時候又會去調用Runnable
接口的實現類對象的run
方法,來執行線程代碼。Thread
類的構造方法,可以接收一個Runnable
接口的子類對象。
2.8 關于Runnable的實現類對象
Runnable
實現類的對象并不是線程對象,因為它沒有start
方法。只有Thread
類對象極其子類對象才是一個線程對象。
近期關于java基礎的同系列文章:
JavaSE基礎知識筆記
Java基礎:面向對象(1)--對象的概念、成員變量與局部變量、匿名對象、類的封裝