詳解Java多線程
文章目錄
1.基本概念:程序、進程、線程
- 程序 : 是爲完成特定任務、用某種語言編寫 的一 組指令的集合 。即指 一
段靜態的代碼 ,靜態對象。 - 進程: 是程序的一次執行過程,或是正在運行的一個程序 。是 一個 動態
的過程 :有 它自身的產生、存在和消亡的 過程 。------ 生命週期
例如:正在運行的QQ,正在播放的音樂播放器
程序是靜態的,進程是 動態的
進程作爲資源分配的單位, 系統在運行時會爲每個進程分配不同的內存區域 - 線程 :進程可進一步細化爲線程,是一個程序內部的一條執行路徑。
若一個進程同一時間並行執行多個線程,就是支持多線程的
線程作爲調度和執行的單位,每個線程擁有獨立的運行棧和程序計數器(pc),線程切換的開銷小
一個進程中的多個線程共享相同的內存單元/內存地址空間〉它們從同一堆中分配對象,可以訪問相同的變量和對象。這就使得線程間通信更簡便、高效。但多個線程操作共享的系統資源可能就會帶來安全的隱患。
比如:360安全衛士,沒運行就是一個程序;運行了,後臺分配資源,是一個進程;當它打開清理垃圾,木馬掃描,全面檢查等線程時,這是可以同時進行的,所以360安全衛士支持多線陳
2.線程的創建和使用
Java 中的線程是通過java.lang.Thread來體現的
Thread構造器
線程創建的4種方法
方式一:繼承Thread類
一般步驟
1)定義子類繼承Thread類。
2)子類中重寫Thread類中的run方法。
3)創建Thread子類對象,即創建了線程對象。
4)調用線程對象start方法:啓動線程,調用run方法。
例子:這裏我們寫一個輸出100內的奇偶數的線程,一個輸出奇數,一個輸出偶數
package com.CharlesLC_Test;
public class Thread_test1 {
public static void main(String[] args) {
//創建線程
Count_even count1 = new Count_even();
//start()的兩個作用: 1.啓動線程;2.執行run()方法
count1.start();
//匿名對象執行start()
new Count_odds().start();
}
}
class Count_even extends Thread{
@Override
public void run() {
for (int i = 0;i<100;i++){
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
class Count_odds extends Thread{
@Override
public void run() {
for (int i = 0;i<100;i++){
if (i % 2 == 1) {
System.out.println(i);
}
}
}
}
其中一段輸出結果如下:
這裏就可以說明:兩個線程(其實這裏有三個線程,main()也算一個)是同時進行的,相當於有兩條執行路徑,CPU在這兩個線程中不斷地切換
這幅圖,成功解釋了這段代碼的運行過程
注意點:
1.如果自己手動調用run()方法,那麼就只是普通方法,類似於調用對象中的普通方法,並沒有開闢一個新的線程,沒有啓動多線程模式。
2.run()方法由JVM調用,什麼時候調用,執行的過程控制都有操作系統的CPU調度決定。
3.想要啓動多線程,必須調用start方法。
4.一個線程對象只能調用一次start()方法啓動,如果重複調用了,則將拋出以上的異常“llegalThreadStateException”。
方式二:實現Runnable接口
1)定義子類,實現Runnable接口。
2)子類中重寫Runnable接口中的run方法。
3)通過Thread類含參構造器創建線程對象。
4)將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造器中。
5)調用Thread類的start方法:開啓線程,調用Runnable子類接口的run方法。
package com.CharlesLC_Test;
public class Thread_Test2 {
public static void main(String[] args) {
Count_Even demo = new Count_Even();
//通過Thread類含參構造器創建線程對象。將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造器中。
Thread count_num = new Thread(demo);
//調用Thread類的start方法:開啓線程,調用Runnable子類接口的run方法。
count_num.start();
}
}
//定義子類,實現Runnable接口
class Count_Even implements Runnable{
@Override
//子類中重寫Runnable接口中的run方法。
public void run() {
for (int i = 0;i<100;i++){
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
較方法的好處:避免了Thread單繼承的缺點;另外,可以多個線程共享一個Runable接口。
方式三:實現callable接口
- 創建一個callable的實現類
- 實現calL方法,將此線程需要執行的操作聲明在call()中
- 創建callable實現類的對象
- 將callable實現類的對象傳遞到FutureTask構造器中,創建Futuretask的對象
- 將創建的Futuretask對象傳到Thread類的構造器中,並調用start()方法
package com.charlesLC_thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class newThread {
public static void main(String[] args) {
//創建callable實現類的對象
Count_odd demo = new Count_odd();
//將callable實現類的對象傳遞到FutureTask構造器中,創建Futuretask的對象
FutureTask futureTask = new FutureTask(demo);
//將創建的Futuretask對象傳到Thread類的構造器中,並調用start()方法
new Thread(futureTask).start();
try {
//若要將返回值的結果輸出,則用get()方法
Object sum = futureTask.get();
System.out.println("總合爲:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//創建一個callable的實現類
class Count_odd implements Callable {
@Override
//實現calL方法,將此線程需要執行的操作聲明在call()中
public Object call() throws Exception {
int num = 0;
for (int i =0;i<100;i++){
if (i%2==0){
num +=i;
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
return num ;
}
}
方式三與方式二對比::
- 與使用Runnable相比,Callable功能更強大些
- 相比run()方法,可以有返回值
- 方法可以拋出異常>支持泛型的返回值
- 需要藉助Future Task類,比如獲取返回結果
方式四:使用線程池來創建
- ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor
- Executors:工具類、線程池的工廠類,用於創建並返回不同類型的線程池
- Executors.newCachedThreadPool():創建一個可根據需要創建新線程的線程池
- Executors.newFixedThreadPool(n);創建一個可重用固定線程數的線程池
- Executors.newSingle ThreadExecutor():創建一個只有一個線程的線程池
- Executors.newScheduled ThreadPool(n):創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行。
package com.charlesLC_thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PoolThread {
public static void main(String[] args) {
//1.提供指定線程數量的線程池
ExecutorService service = Executors.newFixedThreadPool(15);
//2.執行指定的線程的操作。需要提供實現Runnable接口或CalLable接口實現類的對象
service.execute(new num_count());//這裏是runable
service.submit(new num_count2());//這裏是callable
service.shutdown();
}
}
class num_count implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++){
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}}
class num_count2 implements Callable{
@Override
public Object call() throws Exception {
for (int i=0;i<100;i++){
if (i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
return null;
}
}
這裏創建了一個指定提供數量的線程池,定義了兩個線程對象,然後分別在線程池中執行。
Thread類的方法
-
start(): 啓動此線程,並調用run()方法;
-
run(): 線程在被調度時執行的 操作,在調用start()會自動執行。
-
Getname():返回此線程的名字。
在上面的例子中的System.out.println(count_num.getName());
輸出結果:
爲什麼是後面有個數字零呢?
在構造Thread的時候,會自動加上後面的數字,這是遞增的。 -
currentThread (): 靜態方法,返回當前線程 。
-
setName():修改線程的名字
count_num.setName("我是線程1"); System.out.println(count_num.getName());
(利用上面的例子)
成功把線程名改了
- yiled():線程讓步,釋放當前線程的Cpu
暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程
- yiled():線程讓步,釋放當前線程的Cpu
package com.CharlesLC_Test;
public class Thread_test1 {
public static void main(String[] args) {
Count_even count1 = new Count_even();
count1.start();
for (int i =0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
class Count_even extends Thread{
@Override
public void run() {
for (int i = 0;i<100;i++){
if (i % 2 == 0) {
System.out.println(getName()+":"+i);
}
if (i == 20){
yield();
}
}
}
}
但是,雖然這裏讓步了,當時這不說明就把該線程給阻塞了,只是失去了cpu的執行權。有可能cpu在這兩個線程(main和Count_even)的選擇中,還會選擇Count_even
- join():當某個程序執行流中調用其他線程的join()方法時,調用線程將被阻塞,直到join()方法加入的join線程執行完爲止
package com.CharlesLC_Test;
public class Thread_test1 {
public static void main(String[] args) {
Count_even count1 = new Count_even();
count1.start();
for (int i =0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
if (i == 20){
try {
count1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Count_even extends Thread{
@Override
public void run() {
for (int i = 0;i<100;i++){
if (i % 2 == 0) {
System.out.println(getName()+":"+i);
}
}
}
}
這裏main()中的線程被阻塞,等count1執行完,main()纔可以繼續執行。
- sleep(long millis):令當前活動線程在指定時間段內放棄對CPU控制,使其他線程有機會被執行,時間到後重排隊。
- isAlive():返回 boolean,判斷線程是否還活着
線程的調度
- 線程的優先級等級
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 - 涉及的方法
getPriority():返回線程優先值
setPriority(int newPriority):改變線程的優先級
說明
線程創建時繼承父線程的優先級
低優先級只是獲得調度的概率低,並非一定是在高優先級線程之後才執行
例子:
package com.CharlesLC_Test;
public class Thread_test1 {
public static void main(String[] args) {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
Count_even count1 = new Count_even();
Count_odds count2 = new Count_odds();
count1.start();
count2.start();
for (int i =0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
System.out.println(Thread.currentThread().isAlive());
}
}
class Count_even extends Thread{
@Override
public void run() {
setPriority(NORM_PRIORITY);
for (int i = 0;i<100;i++){
if (i % 2 == 0) {
System.out.println(getName()+":"+i);
}
}
}
}
class Count_odds extends Thread{
@Override
public void run() {
setPriority(MAX_PRIORITY);
for (int i = 0;i<100;i++){
if (i % 2 == 1) {
System.out.println(i);
}
}
}
}
這裏我給main中的線程設置優先級爲最小,但是輸出結果,還是main中的數據還是比MAX_PRIORITY等級的count2先輸出;
3.線程的生命週期
新建:當一個Thread類或其子類的對象被聲明並創建時,新生的線程對象處於新建狀態
就緒:處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件,只是沒分配到CPU資源
運行:當就緒的線程被調度並獲得CPU資源時,便進入運行狀態,run()方法定義了線程的操作和功能
阻塞:在某種特殊情況下,被人爲掛起或執行輸入輸出操作時,讓出CPU並臨時中止自己的執行,進入阻塞狀態
死亡:線程完成了它的全部工作或線程被提前強制性地中止或出現異常導致結束
4.線程的同步
由於內容過多,放在新的博客中
鏈接:https://blog.csdn.net/weixin_45640609/article/details/104553708
5.線程的通信
由於內容過多,放在新的博客中
鏈接:https://blog.csdn.net/weixin_45640609/article/details/104555097