線程概述
什麼是線程?
幾乎 所有的操作系統都支持同時運行多個任務,一個任務通常就是一個程序,每個運行中的程序就是一個進程。當一個程序運行時,內部可能包含了多個順序執行流,每個順序執行流就是一個線程。
也可以這麼描述:一個程序運行後至少有一個進程,一個進程可以包含多個線程,但至少要包含一個線程
多線程的優勢
- 多線程編程簡單,效率高(能直接共享數據和資源,多進程不能)(系統城建進程時需要爲該進程重新分配系統資源,但創建線程則代價小很多,因此使用多線程來實現多任務併發比多進程效率高。
- 適合於開發服務程序(如Web服務、聊天服務等)。
- 適合於開發有多種交互接口的程序
- 適合於有人機交互又有計算量的程序
- 減輕編寫交互頻繁、涉及面多的程序的困難(如網絡監聽窗口)
- 程序的吞吐量會得到改善
- 有多個處理器的系統,可以併發運行不同的線程(否則,任何時刻只有一個線程在運行,只是宏觀上看是併發運行)
線程的創建和啓動
繼承Thread類創建線程類
- 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務。因此把run()方法稱爲線程執行
- 創建Thread子類的實例,即創建了線程對象。
- 調用線程對象的start()方法來啓動該線程
package thread;
/**
* @author Mike
* @use 集成thread類創建線程
*/
public class FirstThread extends Thread {
private int i;
public void run(){
for (;i<100;i++){
//當線程集成thread類時,直接使用this即可獲取當前線程
//thread對象的getname()返回當前線程的名字
//因此可以直接調用getname()方法返回當前線程的名字
System.out.println(getName() + " " +i);
}
}
public static void main(String[] args) {
for (int i=0;i<100;i++){
//調用thread的current()方法獲取當前線程
System.out.println(Thread.currentThread().getName()+ " " +i);
if (i==20){
new FirstThread().start();
new FirstThread().start();
}
}
}
}
實現Runnable 接口創建線程類
- 定義Runnable接口創建線程類
- 創建Runnable實現類的實例,並以此實例作爲Thread的target來創建Thread對象,該Thread對象纔是真正的線程對象
- 調用線程對象的start()方法來啓動該線程
package thread;
/**
* @author Mike
* @use 實現Runnable接口創建線程類
*/
public class SecondThread implements Runnable {
private int i;
@Override
public void run() {
for (;i<100;i++){
System.out.println(Thread.currentThread().getName()+ " "+ i);
}
}
public static void main(String[] args) {
for (int i = 0;i<100;i++){
System.out.println(Thread.currentThread().getName()+ " "+ i);
if (i == 20){
SecondThread st = new SecondThread();
//通過new Thread(target,name)方法創建新線程
new Thread(st,"線程1").start();
new Thread(st,"線程2").start();
}
}
}
}
使用Callable 和 Future 創建線程
- 創建Callable接口的實現類,並且實現call()方法,該call()方法將作爲線程的執行主體,可以理解爲有返回值的run()方法
- 使用FutureTask對象作爲Thread對象的target創建並啓動新線程
- 調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值
package thread;
import javax.swing.*;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author Mike
* @use 使用Callable和Future創建線程
*/
public class ThirdThread {
public static void main(String[] args) {
//創建Callable對象
ThirdThread tt=new ThirdThread();
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
int i = 0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+"的循環變量i的值:"+i);
}
//call有返回值
return i;
});
for (int i = 0;i < 100; i++){
System.out.println(Thread.currentThread().getName()+"的循環變量i的值:"+i);
if (i == 20){
new Thread(task,"有返回值的線程").start();
}
}
try{
System.out.println("子線程的返回值:" + task.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
線程的生命週期
線程對象調用了start()方法之後,該線程處於就緒狀態
就緒狀態–>阻塞狀態
- 線程調用sleep()方法主動放棄所佔有的處理器資源
- 線程調用了一個阻塞式IO方法,在該方法返回前,該線程被阻塞
- 線程wait()在等待notify()時會處於阻塞狀態
- 程序調用了線程的suspend方法將線程掛起。但這個方法容易導致死鎖,所以應該儘量避免使用該方法。
阻塞狀態–>就緒狀態
- 調用sleep()方法的線程經過了指定時間
- 線程調用的阻塞式IO方法已經返回
- 線程成功的獲得了試圖取得的同步監視器
- 被notify()喚醒
- 處於掛起狀態的線程被調用了resume()恢復方法
運行狀態–>就緒狀態
yield()方法
線程從阻塞狀態只能進入就緒狀態,無法直接進入運行狀態。就緒和運行之間的狀態轉換通常不受程序控制,而是由系統線程調度所決定。
線程死亡
- run()方法執行完畢,線程正常結束
- 線程拋出一個未捕獲的Exception 或 Error
- 直接調用該線程的stop()方法來結束該線程–該方法容易導致死鎖,通常不推薦使用。
- 不要對處於死亡狀態的線程調用start()方法,程序只能對新建的線程調用start()方法,對新建狀態的線程兩次調用start()方法也是錯誤的。都會引發IllegalThreadStateException異常
控制線程
wait(),notify()
兩個方法配套使用,wait()使得線程進入阻塞狀態
- 當wait()時,必須經過對應的notify(),才能重新進入就緒
- wait(long time),括號中的參數是毫秒值,超出時間或者notigy()調用都能進入就緒狀態
還有一些涉及到同步的內容在寫同步的時候再說吧~
sleep()
sleep()方法可以讓當前正在執行的線程暫停一段時間,並進入阻塞狀態
1.sleep(long time)讓當前正在執行的線程暫停time毫秒,並進入阻塞狀態
2.還有一個sleep(long millis,int nanos)不常用,後面的nanos是微秒
package thread;
import java.util.Date;
/**
* @author Mike
* @use 線程睡眠
*/
public class SleepTest {
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<10;i++){
System.out.println("當前時間: "+ new Date());
Thread.sleep(1000);//注意此處是毫秒爲單位
}
}
}//停一秒輸出一個時間
join()
當在某個程序執行流中調用其他線程的join()方法時,調用線程將被阻塞,直到被join()方法加入的join線程執行完爲止。
- join():等待join進來的線程執行完成
- join(long mills):等待join進來的線程執行mills毫秒,如果她還沒執行完。就不再等了,繼續執行。
package thread;
/**
* @author Mike
* @use 加入線程
*/
public class JoinThread extends Thread {
public JoinThread(String name){
super(name);
}
//重寫run()方法,定義線程執行體
@Override
public void run() {
for ( int i = 0;i < 100;i++){
System.out.println(getName() + " " +i);
}
}
public static void main(String[] args) throws InterruptedException {
new JoinThread("新線程").start();
for (int i = 0 ;i< 100;i++){
if (i == 20){
JoinThread jt = new JoinThread(("join進來的線程"));
jt.start();
jt.join();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
//從結果上來看,剛開始時新線程和main線程在併發執行,有新的線程join進來後,main處於等待狀態,
// 新線程和join線程併發執行
//等join線程和新線程執行完之後繼續執行main線程
//有個問題,join線程先執行完是沒問題的,但是爲什麼新線程會在main線程執行完之前執行完
}
yield()
和sleep()方法相似,他也可以讓當前正在執行的線程暫停,但他不會阻塞該線程,只是將該線程轉入就緒狀態
關於sleep()和yield()的區別
- sleep()方法暫停當前的線程後,會給其他線程執行機會,不會理會其他線程的優先級;但yield()方法只會給更高優先級或者相同優先級的線程執行機會
- sleeep()方法聲明拋出了InterruptException異常,所以調用sleep方法時要麼捕獲該異常,要麼拋出異常,而yield()方法則沒有拋出異常
- sleep()方法比yield()方法有更好的可移植性,通常不建議使用yield()方法來控制併發線程的執行
package thread;
/**
* @author Mike
* @use 線程讓步
*/
public class YieldTest extends Thread {
public YieldTest(String name){
super(name);
}
@Override
public void run() {
for (int i = 0;i<50;i++){
System.out.println(getName()+ " "+i);
if (i == 20){
Thread.yield();
}
}
}
public static void main(String[] args) {
YieldTest yt1 = new YieldTest("高級");
yt1.setPriority(Thread.MAX_PRIORITY);
yt1.start();
YieldTest yt2 = new YieldTest("低級");
yt2.setPriority(Thread.MIN_PRIORITY);
yt2.start();
}
}
線程優先級setPriority(int newPriority)
此處注意優先級並不是絕對的概念,並不是優先級高的一定要執行完纔會執行優先級低的,也並不一定時優先級高的一定先執行,這是一種概率問題,比如優先級高的線程可能有80%的概率會調用執行,優先級低的有20%,但還是有可能會先調用優先級低的。
後臺線程
調用setDaemon(true)方法可以把指定線程設置成後臺線程,如果前臺線程都死亡,後臺線程會自動死亡
package thread;
/**
* @author Mike
* @use 後臺線程
*/
public class DaemonThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++){
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) {
DaemonThread dt = new DaemonThread();
dt.setDaemon(true);
dt.start();
for (int i = 0 ;i < 10 ; i++){
System.out.println(Thread.currentThread().getName()+" "+ i );
}
}
}
//此處發現
下面的鏈接是下一部分關於線程同步的
線程同步學習