Java多線程(一)線程的概念

本文目錄:

進程和線程
線程創建方式
線程的狀態
中斷線程
守護線程

進程和線程

什麼是進程和線程?

在計算機中,一個任務就是一個進程

在進程內部還需要多個子任務,每個子任務被稱爲線程。

一個進程可以包含一個或多個線程(至少一個)。

進程和線程的關係
  1. 一個進程可以包含一個或多個線程(至少一個線程)
  2. 線程是操作系統調度的最小任務單位
  3. 如何調度線程完全由操作系統決定
實現多任務的方法
  1. 多進程模式(每個進程只有一個線程)
  2. 多線程模式(一個進程有多個線程)
  3. 多進程+多線程(複雜度最高)
多進程 vs 多線程
  1. 創建進程比創建線程開銷大
  2. 進程間通信比線程間通信慢
  3. 多進程穩定性比多線程高
Java內置多線程支持
  1. 一個Java程序實際上是一個JVM進程
  2. JVM用一個主線程來執行main()方法
  3. 在main()方法中又可以啓動多個線程
多線程編程特點
  1. 多線程需要讀寫共享數據
  2. 多線程經常需要同步
  3. 多線程編程的複雜度高,調試更困難
Java多線程編程特點
  1. 多線程模型是Java程序最基本的併發模型
  2. 網絡、數據庫、Web等都依賴多線程模型
  3. 必須掌握Java多線程編程才能繼續深入學習

線程創建方式

Thread類

Java爲我們提供了一個Thread類來操作線程,基本使用如下

public class ThreadTest {
  	public static void main(String[] args) {
    	// 創建線程對象
    	Thread thread = new Thread();
    	// 啓動線程
    	thread.start();
  	}
}

線程啓動後虛擬機會自動調用run()方法(自己調用沒有任何意義),直接使用Thread類對象啓動線程run()方法不會執行任何操作,因此需要我們自己定義Thread的子類並重寫run()方法來執行我們自己的操作

class SubThread extends Thread {
  	@Override
    public void run() {
		System.out.println("自定義線程運行中。。。")
    }
}

public class ThreadTest {
  	public static void main(String[] args) {
    	SubThread subThread = new SubThread();
    	subThread.start();
  	}
}
Runnable接口

另一種創建線程的方法是實現Runnable接口,並重寫run()方法

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println("MyThread running...");
    }
}

但是創建線程對象和啓動線程的方法稍有不同

public class Main {
    public static void main(String[] args) {
      	// 創建Runnable的實現類對象
        Runnable runnable = new MyThread();
      	// 通過Runnable實現類對象創建Thread對象
        Thread thread = new Thread(runnable);
      	// 啓動線程
        thread.start();
    }
}
總結
  1. Java用Thread對象表示一個線程,通過調用start()啓動一個線程
  2. 一個線程對象只能調用一次start()
  3. 線程的執行代碼是run()方法
  4. 線程調度由操作系統決定,程序本身無法決定
  5. Thread().sleep()可以把當前線程暫停一段時間

線程的狀態

線程狀態
  1. New(新創建)
  2. Runnable(運行中)
  3. Blocked(被阻塞)
  4. Waiting(等待)
  5. Timed Waiting(計時等待)
  6. Terminated(已終止)
線程終止原因
  1. run()方法執行到return語句返回(線程正常終止)
  2. 因爲未捕獲的異常導致線程終止(線程意外終止)
  3. 對某個線程的Thread實例調用stop()方法強制終止(不推薦)
線程等待

當A線程運行時調用了B線程的join()方法,則會等待B線程執行結束後繼續執行A線程。

public class Main {

    public static void main(String[] args) {
        SubThread subThread = new SubThread();
        subThread.start();
        try {
          	// 主線程會等待subthread執行完成
            subThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
      System.out.println("主線程結束。。。");
    }
}

也可以指定等待的時間,超過等待時間線程仍然沒有結束則不再等待,如果不指定或指定時間爲0則表示一直等待。

public class Main {
    public static void main(String[] args) {
        SubThread subThread = new SubThread();
        subThread.start();
        try {
          	// 等同於subThread.join(0);表示一直等待
          	subThread.join();
          	// 主線程最多等待subthread線程1000ms
            subThread.join(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
      System.out.println("主線程結束。。。");
    }
}			

如果調用join()方法的線程已經運行結束了,則會立即返回不進行等待。

中斷線程

什麼是中斷線程

如果線程需要執行一個長時間任務,就可能需要能中斷線程。

class MyThread extends Thread {
  	@Override
  	public void run() {
    	// 長時間循環執行任務
    	while (true) {
      		// do something...
    	}
  	}
}

中斷線程就是其他線程給該線程發一個信號,該線程收到信號後結束執行run()方法。需要被中斷的線程就是中斷線程。

線程中斷方法
檢測isInterrupted()

中斷線程需要通過檢測isInterrrupted()標誌來決定要不要中斷,其他線程通過調用interrupt()方法中斷該線程。

class MyThread extends Thread {
  	@Override
  	public void run() {
    	// 檢測isInterrupted()標誌決定是否繼續執行
  		while (!isInterrupted()) {
        	System.out.println("running...");
      	}
  	}
}

puclic class Main {
  	public static void main(String[] args) throws Exception {
      	Thread t = new MyThread();
      	t.start();
      	Thread.sleep(1000);
      	// 中斷線程
      	t.interrupt();
    }
}

如果線程處於等待狀態,該線程會捕獲InterruptedException,補貨到該異常說明其他線程對其調用了interrupt()方法,通常情況下該線程應該立刻結束運行。

class MyThread extends Thread {
	public void run() {
	    while(!isInterrupted()) {
	      	System.out.println("running...");
	      	try {
		        // 線程處於等待狀態
		        Thread.sleep(1000);
	      	} catch (InterruptedException e) {
	        	e.printStackTrace();
	        	// 捕獲到中斷異常,立即結束線程運行
	        	return;
			}
	    }
	}
}
自定義running標誌檢測

通過檢測自定義running標誌位來代替檢測isInserrupted(),通過修改running的值來決定線程是否中斷。

class MyThread extends Thread {
	// 自定義一個標識位
	public volatile boolean ruuning = true;
	public void run() {
		// 檢測自定義標識位
		while (running) {
			// do something...
		}
	}
}
public class Main {
	public static void main(String[] args) {
		Thread t = new MyThread();
		t.start();
		Thread.sleep(1000);
		// 修改自定義標識位
		t.running = false;
	}
}

標識位使用volatile來標記,是因爲線程間共享變量需要使用volatile標記,以確保線程能讀取到更新後的變量值。

volatile簡介
Java內存模型

在這裏插入圖片描述

在Java虛擬機中,變量的值保存在主內存中,當線程訪問一個變量的時候,會先獲取一個副本,並且保存到自己的工作內存中,如果線程修改了變量的值,虛擬機會在某個時刻把值回寫到主內存,但這個時間是不固定的。

volatile作用

volatile關鍵字的目的是告訴虛擬機:

  • 每次訪問變量是,總是獲取主內存的最新值

  • 每次修改變量後,立刻回寫到主內存

因此volatile關鍵字解決的是可見性問題:

  • 當一個線程修改了某個共享變量的值,其他線程能夠立刻看到修改後的值
總結
  • 調用interrupt()方法可以中斷一個線程
  • 通過檢測isinterrupted()標識獲取當前線程是否已中斷
  • 如果線程處於等待狀態,該線程會捕獲InterruptedException
  • isInterrupted()爲true或者捕獲了InterruptedException都應該立刻結束
  • 通過標識位判斷需要正確使用volatile關鍵字
  • volatile關鍵字解決了共享變量在線程間的可見性問題

守護線程

Java中所有線程結束後,JVM退出,如果有無限循環(定時任務)的線程,則虛擬機無法退出,這是就需要守護線程。

什麼是守護線程
  • 守護線程是爲其他線程服務的線程
  • 所有非守護線程都執行完畢後,虛擬機退出
特點
  • 守護線程不能持有任何資源(如打開文件等),因爲虛擬機退出時守護線程無法自動釋放資源。
創建守護線程

設置線程屬性setDaemon(true)即可把該線程變爲守護線程。

class TimerThread extends Thread {
	@Override
	public void run() {
    // 無限循環
		while (true) {
			System.out.println(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				break;
			}
		}
	}
}

public class Main {
	public static void main(String[] args) throws Exception {
		System.out.println("Main start");
		TimerThread t = new TimerThread();
		t.start();
		Thread.sleep(5000);
		System.out.println("Main end");
	}
}

因爲子線程在無限循環地打印,所以當Main end輸出後程序並不會結束,子線程還在繼續打印時間,如果把子線程變爲守護線程則程序就正常退出了

//TimerThread類同上
public class Main {
	public static void main(String[] args) throws Exception {
		System.out.println("Main start");
		TimerThread t = new TimerThread();
	    // 設爲守護線程,必須在線程啓動之前,否則會出現異常
	    t.setDaemon(true);
		t.start();
		Thread.sleep(5000);
		System.out.println("Main end");
	}
}

Java中的垃圾回收線程就是一個守護線程,它始終在低級別的狀態下運行,實時監控和管理系統中的可回收資源。當所有非守護線程都退出時,守護線程也就沒有存在的必要了,乖乖的自己離開。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章