前言
當回顧其線程的時候,感覺什麼都忘了,在網上查資料的時候,都沒有什麼好的文章,決定寫一系列關於Java線程的文章。
這篇博客特別基礎,所以推薦一篇寫的比較好文章 —— Java多線程學習(吐血超詳細總結)
線程和進程概述
- 進程:計算機中特定功能的程序在數據集上的一次運行。
- 線程:線程是進程的一個單元。
- 多線程:一個進程中有多個線程在同時運行,如迅雷下載,迅雷軟件的一次運行就是一個進程,那麼在迅雷中可以同時下載多個電影,這就是多線程(每一個下載都是一個線程)
- Jvm是多線程的,在我們運行jvm的時候後臺會運行垃圾回收的線程,來清理沒有被引用的對象。
舉個例子:
進程:啓動一個LOL.exe就叫一個進程。 接着又啓動一個DOTA.exe,這叫兩個進程。
線程:線程是在進程內部同時做的事情,比如在LOL裏,有很多事情要同時做,比如"蓋倫” 擊殺“提莫”,同時“賞金獵人”又在擊殺“盲僧”,這就是由多線程來實現的。
常用創建線程的兩種方式
一種是繼續Thread類,另外一種是實現Runable接口.(其實準確來講,應該有三種,還有一種是實現Callable接口,並與Future、線程池結合使用,此文這裏不講這個,有興趣看這裏Java併發編程與技術內幕:Callable、Future、FutureTask、CompletionService 、BF360C )
使用Thread創建線程的步驟:
- 自定義一個類,繼承java.lang包下的Thread類
- 重寫run方法
- 將要在線程中執行的代碼編寫在run方法中
- 創建上面自定義類的對象
- 調用start方法啓動線程
1. 繼承Thread類
//1.自定義一個類,繼承java.lang包下的Thread類
public class MyThreed extends Thread {
//通過構造函數爲線程起名字
private String name;
public MyThreed(String name) {
this.name = name;
}
//2.重寫run方法
@Override
public void run() {
//3.將要在線程中執行的代碼編寫在run方法中
for (int i = 0; i <=50 ; i++) {
System.out.println(name+"跑了"+i+"米");
}
}
}
測試
public static void main(String[] args) {
//4.創建上面自定義類的對象
MyThreed myThreed=new MyThreed("懶洋洋");
myThreed.start();
MyThreed myThreed1=new MyThreed("灰太狼");
//5.調用start方法啓動線程
myThreed1.start();
System.out.println("方法結束");
}
2. 實現Runnable接口創建線程
使用Runnable創建線程步驟:
- 自定義一個類實現java.lang包下的Runnable接口
- 重寫run方法
- 將要在線程中執行的代碼編寫在run方法中
- 創建自定義類的對象
- 創建Thread對象並將上面自定義類的對象作爲參數傳遞給Thread的構造方法
- 調用start方法啓動線程
// 1.自定義一個類實現java.lang包下的Runnable接口
public class MyRunable implements Runnable {
private String name;
public MyRunable(String name) {
this.name = name;
}
// 2.重寫run方法
@Override
public void run() {
// 3.將要在線程中執行的代碼編寫在run方法中
for (int i = 0; i <=50 ; i++) {
System.out.println(name+"跑了"+i+"米");
}
}
//測試
public static void main(String[] args) {
// 4.創建Thread對象並將上面自定義類的對象作爲參數傳遞給Thread的構造方法
Thread thread = new Thread(new MyRunable("喜洋洋進度"));
Thread thread1 = new Thread(new MyRunable("灰太狼進度"));
//6.調用start方法啓動線程
thread.start();
thread1.start();
}
多線程創建的二種方式對比
繼承Thread
- 優點:可以直接使用Thread類中的方法,代碼簡單
- 缺點:繼承Thread類之後就不能繼承其他的類
實現Runnable接口
- 優點:即時自定義類已經有父類了也不受影響,因爲可以實現多個接口
- 缺點: 在run方法內部需要獲取到當前線程的Thread對象後才能使用Thread中的方法
線程的聲明週期
線程是一個動態執行的過程,它也有一個從產生到銷燬的過程。
下圖顯示了一個線程完整的生命週期。
- 新建: 線程被new出來
- 準備就緒:線程具有執行的資格,即線程調用了start(),沒有執行的權利
- 運行:具備執行的資格和具備執行的權利
- 阻塞:沒有執行的資格和執行權利
- 銷燬: 線程的對象變成垃圾,釋放資源。
線程的併發
我們舉個例子,讓我們更加懂得併發。
現有一家電影院,現哪吒之魔童降世的電影票共有一百張,共有三個窗口賣票,程序怎麼實現?
public class MoviceThreed extends Thread {
//爲線程起別名
private String name;
//共工100張靜態變量的票
static int tickets = 100;
//創建一個鎖對象,這個對象是多個線程對象共享的數據
static Object obj = new Object();
public MoviceThreed(String name) {
this.name = name;
}
@Override
public void run() {
//賣票是持續的
while (true) {
synchronized (obj) {
if (tickets > 0) {
System.out.println(name + "賣出座位是" + (tickets--) + "號");
} else {
break;
}
}
}
System.out.println(name+"賣票結束");
}
}
public class MoviceTest {
public static void main(String[] args) {
MoviceThreed m1 = new MoviceThreed("窗口1");
MoviceThreed m2 = new MoviceThreed("窗口2");
MoviceThreed m3 = new MoviceThreed("窗口3");
m1.start();
m2.start();
m3.start();
}
}
如果我麼用傳統的方式的話會發生併發行爲,可能1號窗口,2號窗口賣出同一張票,這樣是不對的所以我們上面代碼用到 synchronized
-
語法:
synchronized(鎖對象){ //操作共享資源的代碼 }
同步代碼加在什麼地方?
- 代碼被多個線程訪問
- 代碼中有共享的數據
- 共享數據被多條語句操作。