Java中多線程

一、概述

1、多線程和單線程

多線程即一個程序中有多個線程在同時執(zhí)行,單線程和多線程:

單線程:若有多個任務,只有當上一個任務執(zhí)行結束后,下一個任務才開始執(zhí)行

多線程:若有多個任務,可以同時執(zhí)行

2、程序執(zhí)行原理

在操作系統(tǒng)中,有很多種調度方式,這里介紹分時調度和搶占式調度,在Java中使用的是搶占式調度,所以主要介紹搶占式調度方式

分時調度:所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間

搶占式調度:每個線程都有其優(yōu)先級,優(yōu)先讓優(yōu)先級高的進程使用 CPU,如果線程優(yōu)先級相同,則會隨機選擇去執(zhí)行

(1) CPU 使用搶占式調度模式在多個線程間進行著高速的切換

(2) 對于 CPU 的一個核而言,某個時刻只能執(zhí)行一個線程,而 CPU 在多個線程間切換速度相對我們的感覺要快,看上去就是在同一時刻執(zhí)行

(3) 多線程程序并不能提高程序的運行速度,但能提高程序運行效率,讓 CPU 的使用率更高

3、主線程

Java程序在執(zhí)行過程中,先啟動 JVM,并加載對應的 class 文件,JVM 并會從 main 方法開始執(zhí)行我們的程序代碼,一直把 main 方法的代碼執(zhí)行結束。在 JVM 啟動后,必然有一個執(zhí)行路徑(線程)從 main 方法開始,一直執(zhí)行到 main 方法結束,這個線程在 Java 中稱之為主線程。當程序的主線程執(zhí)行時,如果遇到了循環(huán)而導致程序在指定位置停留時間過長,則無法馬上執(zhí)行下面的程序,需要等待循環(huán)結束后才能夠執(zhí)行,那么,能否實現(xiàn)一個主線程負責執(zhí)行其中一個循環(huán),再由另一個線程負責其他代碼的執(zhí)行,最終實現(xiàn)多部分代碼同時執(zhí)行的效果呢?辦法總比困難多,多線程便孕育而生,很好的解決了這個問題!

二、線程的創(chuàng)建

創(chuàng)建新執(zhí)行線程有兩種方法

方法一:

類聲明為 Thread 的子類,該子類應重寫 Thread 類的 run 方法,然后創(chuàng)建對象,開啟線程。run 方法相當于其他線程的 main 方法


方法二:

聲明一個實現(xiàn) Runnable 接口的類,該類然后實現(xiàn) run 方法,然后創(chuàng)建Runnable的子類對象,傳入到某個線程的構造方法中,開啟線程

1、繼承 Thread 類創(chuàng)建線程(方法一)

(1) 創(chuàng)建步驟及分析

定義一個類繼承 Thread 類

重寫 run 方法

創(chuàng)建子類對象,即創(chuàng)建線程對象

調用 star 方法,開啟線程并讓線程執(zhí)行,同時還會告訴 JVM 去調用 run 方法

自定義線程類:

public class myThread extends Thread {

? ? //重寫run方法,完成該線程執(zhí)行的邏輯

? ? public void run()

? ? {

? ? ? ? for(int i = 0;i < 50;i++)

? ? ? ? {

? ? ? ? ? ? System.out.println("新線程正在執(zhí)行" + i);

? ? ? ? }

? ? }

}

在主線程中調用:

public static void main(String[] args)

{

? ? //創(chuàng)建自定義線程對象

? ? myThread mT = new myThread();

? ? //開啟新線程,讓新的線程執(zhí)行程序,jvm調用線程中的run

? ? mT.start();

? ? //在main方法中執(zhí)行

? ? for(int i = 0;i < 50;i++)

? ? {

? ? ? ? System.out.println("main線程正在執(zhí)行" + i);

? ? }

}

分析:

在主線程中創(chuàng)建自定義線程對象,調用star方法開啟新線程,讓新的線程執(zhí)行程序,jvm再調用線程中的run方法

創(chuàng)建新的線程后,會產生兩個執(zhí)行路徑,都會被CPU執(zhí)行,CPU有自己的選擇執(zhí)行權力,所以會出現(xiàn)隨機的執(zhí)行結果

可以理解為兩個線程在搶奪CPU的資源(時間)

注:線程對象調用 run 方法和調用 star 方法的區(qū)別:

(1) 線程對象調用 run 方法不開啟線程,僅僅是對象調用方法

(2) 線程對象調用 star 方法開啟線程,并讓 JVM 調用 run 方法在開啟的線程中執(zhí)行

問題分析:

為什么要繼承 Thread 類,并調用 star 方法才能開啟線程,為什么不像下面代碼一樣直接創(chuàng)建 Thread 類的對象呢?

Thread t1 = new Thread();

t1.star();

創(chuàng)建線程的目的是為了建立程序單獨的執(zhí)行路徑,讓多部分代碼實現(xiàn)同時執(zhí)行,而以上代碼,直接創(chuàng)建 Thread 類的對象,雖然沒有語法錯誤,不會報錯,但該 star 調用的是Thread 類中的 run 方法,而這個方法什么也沒做,更重要的是這個 run 方法中并沒有定義我們需要讓線程執(zhí)行的代碼

Thread 類 run 方法中的任務并不是我們所需要的,只有重寫這個 run 方法,Thread 類已經定義了線程任務的編寫位置(run 方法),那么只要在 run 方法中定義任務代碼即可,即重寫 run 方法

(2) 多線程內存理解

多線程在執(zhí)行的時候,是在棧內存中的,每一個執(zhí)行線程都有一片自己的所屬棧內存空間,進行方法的壓棧和出棧

當執(zhí)行線程的任務結束了,線程自動在棧內存中釋放,當所有的執(zhí)行線程都結束了,進程也就結束了

(3) 獲取和設置線程名稱

String getName():返回該線程的名稱(同 Thread.currentThread().getName())

Thread.currentThread():獲取當前線程對象

Thread.currentThread().getName():獲取當前線程對象的名稱

setName():設置線程名稱

public static void main(String[] args)

{

? ? myThread mT = new myThread();

? ? mT.start();

? ? mT.setName("oneStar");? ? //設置線程名稱

? ? //System.out.println(getName());? //使用getName()方法獲取線程名稱 在新線程中可以這樣使用

? ? System.out.println(Thread.currentThread().getName());? //使用Thread.currentThread().getName()獲取線程名稱

? ? System.out.println(Thread.currentThread()); //獲取當前線程對象

}

(4)?Thread 類 sleep 方法

sleep 方法是一個靜態(tài)的方法,可以通過 Thread 類直接調用,會有異常拋出

public static void main(String[] args) {

? ? myThread mT = new myThread();

? ? mT.start();

? ? //使用sleep方法延時打印for循環(huán)

? ? for(int i = 0;i < 5;i++)

? ? {

? ? ? ? try {

? ? ? ? ? ? Thread.sleep(1000);

? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? ? ? System.out.println(i);

? ? }

}

2、實現(xiàn) Runnable 接口創(chuàng)建線程(方法二)

Runnable 接口用來指定每一個線程要執(zhí)行的任務,包含了一個 run 的無參數(shù)抽象方法,需要由接口實現(xiàn)重寫該方法。此創(chuàng)建線程的方法是聲明實現(xiàn) Runnable 接口的類,該類實現(xiàn) run 方法,然后創(chuàng)建 Runnable 的子類對象,傳入到某個線程的構造方法中,開啟線程

(1) 創(chuàng)建步驟:

定義類實現(xiàn) Runnable 接口

重寫接口中的 run 方法

創(chuàng)建 Thread 類的方法

將 Runnable 接口的子類對象作為參數(shù)傳遞給 Thread 類的構造函數(shù)

調用 Thread 類的 star 方法開啟線程

定義實現(xiàn)類接口

//定義實現(xiàn)類接口

public class myRunnable implements Runnable {

? ? //重寫run方法

? ? public void run()

? ? {

? ? ? ? for(int i = 0;i < 5;i++)

? ? ? ? {

? ? ? ? ? ? System.out.println("myRunnable線程正在執(zhí)行!");

? ? ? ? }

? ? }

}

在主線程中調用

public static void main(String[] args)

{

? ? //創(chuàng)建線程執(zhí)行目標類對象

? ? myRunnable mR = new myRunnable();

? ? //將Runnable接口的子類對象作為參數(shù)傳遞給Thread類的構造函數(shù)

? ? Thread t1 = new Thread(mR);

? ? Thread t2 = new Thread(mR);

? ? //開啟線程

? ? t1.start();

? ? t2.start();

? ? for(int i = 0;i < 5;i++)

? ? {

? ? ? ? System.out.println("main線程正在執(zhí)行!");

? ? }

}

(2)?實現(xiàn) Runnable 接口的好處

避免了單繼承的局限性,所以此方法較為常用

將線程分為兩部分,一部分線程對象,一部分線程任務,更加符合面向對象思想(繼承 Thread 類線程對象和線程任務耦合在一起)

將線程任務單獨分離出來封裝成對象,類型就是 Runnable 接口類型

Runnable 接口對線程對象和線程任務進行解耦,降低緊密性或依賴性,創(chuàng)建線程和執(zhí)行任務不綁定

三、線程的匿名內部類

使用線程的匿名內部類方式,可以方便的實現(xiàn)每個線程執(zhí)行不同線程任務操作

方法一:重寫 Thread 類中的方法創(chuàng)建線程

new Thread() {

? ? public void run() {

? ? ? ? for (int x = 0; x < 40; x++) {

? ? ? ? ? ? System.out.println(Thread.currentThread().getName()

? ? ? ? ? ? ? ? ? ? + "...X...." + x);

? ? ? ? }

? ? }

}.start();

方法二:使用匿名內部類的方式實現(xiàn) Runnable 接口,重寫 Runnable 接口中的 run 方法

Runnable r = new Runnable() {

? ? public void run() {

? ? ? ? for (int x = 0; x < 40; x++) {

? ? ? ? ? ? System.out.println(Thread.currentThread().getName()

? ? ? ? ? ? ? ? ? ? + "...Y...." + x);

? ? ? ? }

? ? }

};

new Thread(r).start();

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容