Java線程編程基礎 第一章
1、本章任務:建立多線程程序
2、本章知識點:
- 瞭解Java線程模型
- 掌握創建線程的方法
- 設置線程優先級
3、多線程編程概述
- Java提供了對多線程編程的內置支持
- 多線程程序包括能夠併發運行的兩個或多個代碼段
單線程、多線程對比示例圖:
從圖中可以看出:
1.單線程中的3段代碼只能在不同的時間點執行,
2.多線程中的3段代碼可以在同一時間點執行
4、進程與線程
- 進程:一個進程是 一個正在執行的程序 ,目前的操作系統大多都支持同時運行兩個或多個程序,稱爲多任務操作系統
- 線程:單個程序 可以同時執行 兩個或更多的任務 ,每個任務稱爲 一個線程 ,進程包含線程
- 進程是重量級的任務 ,每個進程都有自己獨立的地址空間
- 線程是輕量級的任務 ,他們共享一個地址空間,共享同一個進程
- 進程間通信代價昂貴而且受限,線程通信很容易
- 利用多線程可使你的程序最大限度地利用CPU,因爲空閒時間被限制在最小
5、 Thread類和Runnable接口
Java的多線程系統構建在Thread類 、方法以及Runnable接口 之上。
可以通過繼承Thread類或者實現Runnable接口創建一個新線程
Thread類定義了幾個管理線程的方法,常用方法如下:
方法名 功能
====================================
getName 獲取線程名
getPriority 獲得線程的優先級
isAlive 判定線程是否仍在運行
join 等待一個線程終止
run 線程入口方法
sleep 暫停一個線程一段時間
start 啓動線程
6、程序的主線程
6.1)主線程描述
Java程序啓動時 (從main方法開始),一個線程 立刻開始運行,這個線程稱
爲主線程 ,主線程的重要性體現在如下兩個方面:
1)它是產生其他子線程的線程
2)一般情況下,必須是最後一個結束 執行的線程,
因爲它要執行各種關閉操作
主線程不但在程序開始時自動創建,也能通過Thread對象來控制,此時需要
調用:Thread.currentThread()
6.2)主線程控制示例代碼
public static void main(String args[ ]){
Thread t = Thread.currentThread( ); //獲取當前線程(主線程)
System.out.println("當前線程:" + t); //輸出:線程名-優先級-線程組名
//改變主線程名稱
t.setName("My Thread"); System.out.println("改變之後的名稱:" + t);
//打印數字1 ~ 5,每隔1秒打印1次
try{
for(int i = 1; i <= 5; i++){
System.out.println(i);
Thread.sleep(1000); //休眠1000毫秒
}
}catch(InterruptedException e){
System.out.println( " 主線程中斷! " );
}
}
7、創建線程
Java定義了兩種 創建線程的方法 :
1)可以實現Runnable接口 ,這是創建線程最簡單的方法,實現Runnable接口
只需一個簡單的run()方法,其聲明如下:
public void run();
run()方法是線程的進入點,線程在run方法返回時結束
2)可以繼承Thread類 ,重寫Thread類的run()方法
8、創建線程 方法1:實現Runnable接口
/**
* 線程類MyThread,實現Runnable接口
*/
public class MyThread implements Runnable{ //線程類
//線程入口點
public void run(){
try{
//打印數字1 ~ 5,每隔500毫秒打印一次
for(int i = 1; i <= 5; i++){
System.out.println("子線程打印:" + i);
Thread.sleep(500); //休眠500毫秒
}
}catch(InterruptedException e){
System.out.println("子線程中斷!");
}
System.out.println("子線程執行結束!");
}
}
/**
* 線程類MyThread的測試類
*/
public class TestMyThread{
public static void main(String args[ ]){
//創建線程
Thread t = new Thread(new MyThread(), "Demo Thread");
System.out.println("子線程被創建:" + t); //顯示線程信息
t.start(); //啓動線程
//主線程中間隔1秒,打印數字1 ~ 5
try{
for(int i = 1; i <= 5; i++){
System.out.println("主線程打印:" + i);
Thread.sleep(1000); //休眠1000毫秒
}
}catch(InterruptedException e){
System.out.println("主線程中斷!");
}
System.out.println("主線程執行結束!");
}
}
9、創建線程 方法2:繼承Thread類
/**
* 線程類MyThread1,繼承Thread類
*/
public class MyThread1 extends Thread{
public MyThread1(){
super("Demo Thread"); //線程命名
}
//重寫線程入口方法
public void run(){
try{
//打印數字1 ~ 5,每隔500毫秒打印一次
for(int i = 1; i <= 5; i++){
System.out.println("子線程打印:" + i);
Thread.sleep(500); //休眠500毫秒
}
}catch(InterruptedException e){
System.out.println("子線程中斷!");
}
System.out.println("子線程執行結束!");
}
}
/**
* 線程類MyThread1的測試類
*/
public class TestMyThread1{
public static void main(String args[ ]){
//創建線程
Thread t = new MyThread1();
System.out.println("子線程被創建:" + t); //顯示線程信息
t.start(); //啓動線程
//主線程中間隔1秒,打印數字1 ~ 5
try{
for(int i = 1; i <= 5; i++){
System.out.println("主線程打印:" + i);
Thread.sleep(1000); //休眠1000毫秒
}
}catch(InterruptedException e){
System.out.println("主線程中斷!");
}
System.out.println("主線程執行結束!");
}
}
10、如何選擇創建線程的方式
問: Java有兩種創建線程的方式,哪種更好?
1. Thread類定義了多個派生類可以重寫的方法,run()方法只是其中之一,
所以只有在需要增強或者修改Thread類時才應該使用繼承Thread類方式
2. 如果不想重寫Thread類的run()方法外的其他方法,最好還是簡單的
實現Runnable接口(推薦)
11、線程的狀態
- 準備運行狀態:ready to run
- 運行狀態:running
- 暫停狀態:suspended 運行中的線程可以暫停,暫停的線程可以重新啓動
- 阻塞狀態:blocked 線程在等待其他資源時被阻塞
(這個狀態和視頻播放器的操作非常類似,: )
注意:線程在任何時候都能被終止,一旦終止就不能被重新激活
12、啓動多個線程
/**
* 改進的MyThread線程類(改爲自啓動線程)
*/
public class MyThread implements Runnable {
private Thread t;//線程對象
public Thread getT() {
return t;
}
/**構造方法*/
public MyThread(String threadName){
//創建線程對象
this.t = new Thread(this, threadName);
System.out.println("創建子線程:" + threadName);
//啓動線程
this.t.start();
}
/**
* 功能:線程入口方法
*/
public void run() {
Thread t = Thread.currentThread();
try {
//打印數字1 ~ 5,間隔1秒
for(int i = 1; i <= 5; i++){
System.out.println(t.getName() + " 打印:" + i);
//休眠1秒
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("子線程中斷!");
}
System.out.println("子線程結束!");
}
}
/**
* 多線程測試類
*/
public class TestMyThread {
public static void main(String[] args) {
//創建線程對象1
MyThread t1 = new MyThread("子線程1");
//創建線程對象2
MyThread t2 = new MyThread("子線程2");
try {
//主線程打印數字1 ~ 5,間隔1秒
for(int i = 1; i <= 5; i++){
System.out.println("主線程打印:" + i);
//休眠1秒
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("主線程中斷!");
}
System.out.println("主線程結束!");
}
}
13、使用isAlive() 和join()
問題: 主線程一般要最後結束,之前的示例中是通過在main方法中加入sleep()
使主線程休眠足夠長的時間以確保主線程最後結束,這個解決方式合理嗎?怎樣
才能知道子線程是否終止?或者說怎樣才能保證主線程最後結束?
答案: 有兩種方式可以確定一個線程是否結束:
1)在線程中調用isAlive(),這個方法由Thread定義,如果它調用的線程仍在運行,
返回true,否則返回false
2)使用join()方法來等待另一個線程的結束,該方法一直等待直到它調用的線程終止
14、isAlive()和jion()示例
嘗試運行以下代碼並觀察主線程是否最後結束:
public static void main(String[] args) {
MyThread t1 = new MyThread("子線程1"); // 創建線程1
MyThread t2 = new MyThread("子線程2"); // 創建線程2
MyThread t3 = new MyThread("子線程3"); // 創建線程3
//顯示線程是否運行
System.out.println("子線程1是否運行:" + t1.getT().isAlive());
System.out.println("子線程2是否運行:" + t2.getT().isAlive());
System.out.println("子線程3是否運行:" + t3.getT().isAlive());
// 主線程等待子線程結束
try {
System.out.println("等待子線程結束…");
t1.getT().join(); // 等待t1線程結束
t2.getT().join(); // 等待t2線程結束
t3.getT().join(); // 等待t3線程結束
} catch (InterruptedException e) {
System.out.println("主線程中斷!");
}
//顯示線程是否運行
System.out.println("子線程1是否運行:" + t1.getT().isAlive());
System.out.println("子線程2是否運行:" + t2.getT().isAlive());
System.out.println("子線程3是否運行:" + t3.getT().isAlive());
System.out.println("主線程執行結束!");
}
15、線程優先級概述
Java給每個線程分配一個優先級,以決定哪個線程可以優先分配CPU時間
優先級是一個整數,用於指定線程的相對優先程度
優先級可以決定什麼時候從一個運行中的線程切換到另一個線程,切換規則如下:
- 一個線程自願釋放控制(放棄、睡眠或者阻塞),所有其他線
程被檢查,高優先級線程被分配CPU時間 - 一個線程可以被另一個高優先級線程搶佔資源,稱爲搶佔式多任務
注意:具有同一優先級的線程競爭CPU時間,不同操作系統的處理方式上存在差別
16、線程優先級設置
- 線程調度器使用線程優先級以決定什麼時候允許運行
- 理論上高優先級的線程將比低優先級的線程得到更多CPU時間
- 低優先級線程在運行時,高優先級的線程會搶佔低優先級線程的執行權
-
設置線程優先級,使用setPriority()方法:
final void setPriority(int level);
參數level爲線程的優先級 ,其值在1 ~ 10 之間
Thread類提供瞭如下常量方便優先級的設置
Thread.MIN_PRIORITY == 1
Thread.MAX_PRIORITY == 10
Thread.NORM_PRIORITY == 5
17、線程優先級示例
/**
* 功能:計數器線程
*/
public class Clicker implements Runnable {
private int click = 0;//計數值
private Thread t;//線程對象
private volatile boolean running = true;//運行開關
public int getClick() {
return click;
}
public Thread getT() {
return t;
}
/**構造方法,參數爲優先級*/
public Clicker(int priority){
this.t = new Thread(this);
this.t.setPriority(priority);//設置線程優先級
}
public void run() {
while(this.running){
this.click++;//計數器累加
}
}
public void start(){
this.t.start();//啓動線程
}
public void stop(){
this.running = false;//結束線程
}
}
/**
* 計數器線程測試類
*/
public class TestClicker {
public static void main(String[] args) {
//創建線程,設置4種檔次的優先級
Clicker hi = new Clicker(Thread.NORM_PRIORITY + 2);
Clicker hi1 = new Clicker(Thread.NORM_PRIORITY + 4);
Clicker lo = new Clicker(Thread.NORM_PRIORITY - 2);
Clicker lo1 = new Clicker(Thread.NORM_PRIORITY - 4);
//啓動線程
hi.start();
hi1.start();
lo.start();
lo1.start();
try {
Thread.sleep(2000);//休眠2秒
//結束線程
hi.stop();
hi1.stop();
lo.stop();
lo1.stop();
//等待線程結束
hi.getT().join();
hi1.getT().join();
lo.getT().join();
lo1.getT().join();
} catch (InterruptedException e) {
System.out.println("主線程中斷!");
}
//顯示計數器值
System.out.println("lo1計數器:" + lo1.getClick());
System.out.println("lo計數器:" + lo.getClick());
System.out.println("hi計數器:" + hi.getClick());
System.out.println("hi1計數器:" + hi1.getClick());
System.out.println("主線程結束!");
}
}
18、總結
- 進程與線程的區別?
- 如何獲取主線程?
- 創建線程的兩種方式?
- 獲知線程是否運行使用哪個方法?
- 等待線程執行結束使用哪個方法?
- 線程優先級的範圍是什麼?