文章目錄
併發和並行
併發:指兩個或多個事件在同一個時間段內發生(交替執行)
並行:指兩個或多個事件在同一時刻發生(同時發生,多個CPU)
線程和進程
進程:指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創建、運行到消亡的過程。
線程:線程是進程中的一個執行單元,負責當前進程中程序的執行,一個進程中至少有一個線程。一個進程中是可以有多個線程的,這個應用程序也可以稱之爲多線程程序
線程的調度
分時調度
所有線程輪流使用CPU的使用權,平均分配每個線程佔用CPU的時間
搶佔式調度
優先讓優先級高的線程使用CPU,如果線程優先級相同,那麼會隨機選擇一個(線程隨機性),Java使用的爲搶佔式調度
可以在任務管理器中設置優先級
主線程
主線程:執行主(main)方法的線程
單線程程序:java程序中只有一個線程
執行從main方法開始,從上到下依次執行
創建線程類
Java使用java.lang.Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例。
創建多線程程序的第一種方式:創建Thread類的子類
實現步驟:
- 創建一個Thread類的子類
- 在Thread類的子類種重寫Thread類種的run方法,設置線程任務(開啓線程要做什麼)
- 創建Thread類的子對象
- 調用Thread類中的start方法,開啓新的線程,執行run方法
public void start()使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
結果是兩個線程併發地運行;當前線程(main線程)和另一個線程(創建的新線程,執行其 run 方法)。
多次啓動一個線程是非法的。特別是當線程已經結束執行後,不能再重新啓動。
java程序屬於搶佔式調度,哪個線程的優先級高,哪個線程優先執行;同一個優先級,隨機選擇一個執行
// 1. 創建一個Thread類的子類
public class MyThread extends Thread {
// 2. 在Thread類的子類中重寫Thread類中的run方法,設置線程任務
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("run:"+i);
}
}
}
public class Demo01Thread {
public static void main(String[] args) {
// 3. 創建Thread類的子對象
MyThread mt = new MyThread();
// 4. 調用Thread類中的start方法,開啓新的線程,執行run方法
mt.start();
for (int i = 0; i < 5; i++) {
System.out.println("main:"+i);
}
}
}
出現隨機性打印結果
多線程內存圖解
當調用mt.start()方法,就開闢新的佔空間,程序變成多線程,由cpu去做選擇
Thread類的常用方法
獲取線程的名稱
- 使用Thread類中的方法getName().
String getName()
- 可以先獲取到當前正在執行的線程,使用線程中的方法getName()獲取線程的名稱
public static Thread currentThread()返回對當前正在執行的線程對象的引用。
public class MyThread extends Thread{
// 重寫run()方法,設置線程任務
@Override
public void run() {
// 1. 使用Thread類中的方法getName().
// String name = getName();
// System.out.println(name); // 結果形式 Thread-1
// 2. 可以先獲取到當前正在執行的線程,使用線程中的方法getName()獲取線程的名稱
Thread t = Thread.currentThread();
System.out.println(t); // 結果形式 Thread[Thread-1,5,main]
// 鏈式編程
System.out.println(Thread.currentThread().getName()); // 結果形式 Thread-1
}
}
public class Demo01GetThreadName {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
new MyThread().start();
new MyThread().start();
// 鏈式編程
System.out.println(Thread.currentThread().getName());//main
}
}
設置線程的名稱
- 使用Thread類中的方法setName(名字)
public final void setName(String name)改變線程名稱,使之與參數 name 相同。
- 創建一個帶參數的構造方法,參數傳遞線程的名稱,調用父類的帶參構造方法,把線程名稱傳遞給父類,讓父類(Thread)給子線程起一個名字
Thread(String name) 分配新的 Thread 對象。
public class MyThread extends Thread{
// 重寫run()方法,設置線程任務
public MyThread(){}
public MyThread(String name){
super(name);//把線程名稱傳遞給父類,讓父類(Thread)給子線程起一個名字
}
@Override
public void run() {
// 獲取線程名稱
System.out.println(Thread.currentThread().getName());
}
}
public class Demo01GetThreadName {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.setName("小強");
mt.start();
new MyThread("旺財").start();
}
}
sleep
public static void sleep(long millis) :使當前正在執行的線程以指定的毫秒數暫停(暫時停止執行)。毫秒數結束之後,線程繼續執行。
throws InterruptedException在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。該線程不丟失任何監視器的所屬權。
public class Demo02Sleep {
public static void main(String[] args) {
for (int i = 0; i < 60; i++) {
System.out.println(i);
try {
Thread.sleep(1000);//單位是毫秒
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
創建多線程程序的第二種方式:聲明實現 Runnable 接口的類
Runnable 接口應該由那些打算通過某一線程執行其實例的類來實現。類必須定義一個稱爲 run 的無參數方法。
java.lang.Thread類的構造方法
Thread(Runnable target) :分配新的 Thread 對象。
Thread(Runnable target, String name) :分配新的 Thread 對象。
實現步驟:
- 創建一個Runnable接口的實現類
- 在實現類中重寫Runnable接口的run方法,設置線程任務
- 創建一個Runnable接口的實現類對象
- 創建Thread類對象,構造方法中傳遞Runnable接口的實現類對象
- 調用Thread類中的start方法,開啓新的線程執行run方法
//1. 創建一個Runnable接口的實現類
public class RunnableImpl implements Runnable {
// 2. 在實現類中重寫Runnable接口的run方法,設置線程任務
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
public class Demo01Runnable {
public static void main(String[] args) {
//3. 創建一個Runnable接口的實現類對象
RunnableImpl run = new RunnableImpl();
//4. 創建Thread類對象,構造方法中傳遞Runnable接口的實現類對象
Thread t = new Thread(run);
// 5. 調用Thread類中的start方法,開啓新的線程執行run方法
t.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
Thread和Runnable的區別
- Runnable避免了單繼承的侷限性
一個類只能繼承一個類,繼承了Thread類就不能繼承其他類
實現了Runnable接口,還可以繼承其他的類,實現其他的接口 - Runnable增強了程序的擴展性,降低了程序的耦合性(解耦)
實現Runnable接口的方式,把設置線程任務和開啓新線程進行了分離(解耦)
實現類中,重寫了run方法:用來設置線程任務
創建Thread對象,調用start方法,用來開啓新線程
匿名內部類
匿名內部類方式實現線程的創建
匿名:沒有名字
內部類:寫在其他類內部的類
匿名內部類作用:簡化代碼
把子類繼承父類,重寫父類的方法,創建子類對象合一步完成
把實現類實現接口,重寫接口中的方法,創建實現類對象合成一步完成
匿名內部類的最終產物是:子類/實現類對象,而這個類沒有名字
格式:
new 父類/接口(){
重寫父類/接口中的方法
};
public class Demo01InnerClassThread {
public static void main(String[] args) {
new Thread(){
// 重寫run方法
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"AAA");
}
}
}.start();
// 線程的接口Runnable
Runnable r = new Runnable(){
//重寫run方法
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"BBB");
}
}
};
new Thread(r).start();
//簡化接口的方式
new Thread(new Runnable(){
//重寫run方法
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"CCC");
}
}
}).start();
}
}
注意事項
- 匿名內部類,在【創建對象】的時候,只能使用唯一一次。
- 匿名對象,在【調用方法】的時候,只能調用唯一一次。
如果希望同一個對象,調用多次方法,那麼必須給對象起個名字 - 匿名內部類是省略了【實現類/子類名稱】,但是匿名對象是省略了【對象名稱】
強調:匿名內部類和匿名對象不是一回事!!!