java之初學線程
線程
學習線程相關的筆記,前面寫過關於很多線程的使用,有興趣的可以去了解下
線程
概念理解
- 並發 : 指兩個或多個事件在同一個時間段內發生(交替執行)。
- 並行 : 指兩個或多個事件在同一時刻發生(同時發生)。
- 進程 : 是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多
個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創
建、運行到消亡的過程。 線程 : 進程內部的一個獨立執行單元;一個進程可以同時並發的運行多個線程,可以理解為一個進程便相當
於一個單 CPU 操作系統,而線程便是這個系統中運行的多個任務(一個程序至少有一個進程,一個進程可以有多個線程)。線程和進程的區別 :
1.進程:有獨立的內存空間,進程中的數據存放空間(堆空間和棧空間)是獨立的,至少有一個線程。
2.線程:堆空間是共享的,棧空間是獨立的,線程消耗的資源比進程小的多。
多線程的原理
從圖中可以看出,線程的啟動實際上是開辟了新的空間,這樣的話jvm就可以在U盾謳歌棧之間相互切換,這個就是多線程的原理.
Runnable接口
Runnable是多線程的祖宗類,多線程都間接或直接的實現了這個接口.內部只有run方法,所以也可以看出run方法時多線程的核心.
Thread類
先了解一下Thread類的內部方法:
構造方法:
- public Thread() :分配一個新的線程對象。
- public Thread(String name) :分配一個指定名字的新的線程對象。
- public Thread(Runnable target) :分配一個帶有指定目標新的線程對象。
- public Thread(Runnable target,String name) :分配一個帶有指定目標新的線程對象並指定名字。
常用方法:
- public String getName() :獲取當前線程名稱。
- public void start() :導致此線程開始執行; Java虛擬機調用此線程的run方法。
- public void run() :此線程要執行的任務在此處定義代碼。
- public static void sleep(long millis) :使當前正在執行的線程以指定的毫秒數暫停(暫時停止執行)。
- public static Thread currentThread() :返回對當前正在執行的線程對象的引用。
使用Thread來創建線程
- 創建Thread類的子類
- 在Thread類的子類中重寫run方法,設置線程任務
- 創建Thread類的子類
- 調用Thread類的方法start方法,開啟新的線程,執行run方法.(這需要註意,是調用start方法,而不是直接調用run方法)
void start() :導致此線程開始執行; Java虛擬機調用此線程的run方法。結果是兩個線程並發執行,當前線程(main)
和另一個線程(創建的新線程,執行其run方法),多次啟動一個線程是非法的.特別當線程已經結束後,不能重新啟動.java程序
是搶占式調度,哪個線程優先級高,哪個線程先執行;同一個優先級,隨機選擇一個.
獲取線程名稱
獲取線程的名稱有兩種方法:
- 使用getName()獲取線程名稱,這個需要使用Thread類的子類對象來調用.
- 使用Thread.currentThread().getName()獲取線程名稱(推薦使用)
設置線程名稱
設置線程的名稱也有兩種方式:
- 使用setName()方法設置線程名稱,也是需要Thread類或者子類的對象調用.
- 使用構造方法,可以使用有參構造來設置線程名稱.
Runnable接口
- 定義一個類,實現Runnable接口(任務類).
- 重寫Runnable的run方法
- 創建任務類對象
- 創建線程類對象,並將任務對象參數作為參數進行傳遞
使用線程類對象調用start方法啟動線程.
這個過程中需要註意的是第四步,需要使用public Thread(Runnable target) :分配一個帶有指定目標新的線程對象。
public Thread(Runnable target,String name) :分配一個帶有指定目標新的線程對象並指定名字。這兩個構造方法.
創建線程的方式
實現Runnable接口
/** * 使用實現Runnable接口實現多線程 * * @author WZLOVE * @create 2018-07-16 19:46 */ public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }
繼承Thread類
/** * 使用繼承實現多線程 * * @author WZLOVE * @create 2018-07-16 19:47 */ public class MyThread extends Thread{ public MyThread() { } public MyThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + ":" + i); } } }
不同的方法實現多線程,創建對象使用的方式也不同:
/** * @author WZLOVE * @create 2018-07-16 19:45 */ public class ThreadDemo { public static void main(String[] args) { // 繼承創建對象相對簡單 MyThread mt = new MyThread("繼承"); mt.start(); // 實現接口創建對象 // 創建自定義類對象 MyRunnable mr = new MyRunnable(); // 創建線程對象 Thread thread = new Thread(mr,"實現接口"); thread.start(); } }
註意點:
1.實際上,Thread類也實現了Runnable接口.
2.所有的多線程代碼都在run方法裏面
3.所有的多線程代碼都是通過運行Thread的start()方法來運行的
4.Runnable對象僅僅作為Thread對象的target,Runnable實現類裏包含的run()方法僅作為線程執行體。
而實際的線程對象依然是Thread實例,只是該Thread線程負責執行其target的run()方法。
實現Runnable接口與繼承Thread類的不同點
使用接口比類是更有優勢的:
- 可以避免java中的單繼承的局限性。
- 增加程序的健壯性,實現解耦操作,代碼可以被多個線程共享,代碼和線程獨立(任務類和線程類進行分離,解耦)。
- 但是實現接口不能直接使用Thread類的方法,但是可以通過獲取當前線程對象進行調用方法.
- 適合多個相同的程序代碼的線程去共享同一個資源。
- 線程池只能放入實現Runable或Callable類線程,不能直接放入繼承Thread的類。
線程的安全問題的產生
線程的安全問題的產生是由於多個線程對共享資源的訪問.簡單的說線程安全問題都是由全局變量及靜態變量引起的。
若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;
若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
買電影票的例子(出現安全問題,運行一下):
package com.wzlove.thread.movie;
/**
* 票數的任務類
*
* @author WZLOVE
* @create 2018-07-17 14:35
*/
public class TicketRunnableImpl implements Runnable{
/**
* 定義共享資源
*/
private int ticket = 100;
@Override
public void run() {
while (true){
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在賣第"+ ticket -- + "張票");
}
}
}
}
package com.wzlove.thread.movie;
/**
* 多線程安全問題的測試
*
* @author WZLOVE
* @create 2018-07-17 14:37
*/
public class MovieDemo {
public static void main(String[] args) {
// 創建任務類對象
TicketRunnableImpl ticketRunnable = new TicketRunnableImpl();
// 創建線程對象
Thread t1 = new Thread(ticketRunnable,"窗口1");
Thread t2 = new Thread(ticketRunnable,"窗口2");
Thread t3 = new Thread(ticketRunnable,"窗口3");
// 開始線程
t1.start();
t2.start();
t3.start();
}
}
上面的情況就會出現線程安全問題.
線程的安全問題的解決
使用同步解決線程的安全問題.
- 同步代碼塊
- 同步方法
- lock鎖機制
1.同步代碼塊
格式 :
// 同步對象可一是任意對象,推薦使用this
synchronized(同步對象){
可能出現同步問題的代碼
}
鎖對象可以是任意對象,但是鎖對象必須唯一.
2.同步方法
格式:
// 同步方法也是有鎖對象的,也就是當前對象this
修飾符 synchronized 返回值類型 方法名(參數列表){
可能出現同步問題的代碼
}
需要註意的是靜態同步方法內的同步鎖不是this(因為靜態代碼的執行在this之前產生),而是類的字節碼對象,也就是(類名.class)
3.Lock鎖機制
- public void lock() :加同步鎖。
- public void unlock() :釋放同步鎖。
使用步驟:
- 創建ReentrantLock的對象
- 在使用共享資源前使用lock方法進行加鎖
- 在使用共享資源結束後使用unlock方法釋放同步鎖
線程狀態的概述
線程狀態 | 導致狀態發生條件 |
---|---|
new()新建 | 線程剛被創建,但是並未啟動。還沒調用start方法。 |
Runnable(可運行) | 線程可以在java虛擬機中運行的狀態,可能正在運行自己代碼,也可能沒有,這取決於操 作系統處理器。 |
Blocked(鎖阻塞) | 當一個線程試圖獲取一個對象鎖,而該對象鎖被其他的線程持有,則該線程進入Blocked狀 態;當該線程持有鎖時,該線程將變成Runnable狀態。 |
Waiting(無限等待) | 一個線程在等待另一個線程執行一個(喚醒)動作時,該線程進入Waiting狀態。進入這個 狀態後是不能自動喚醒的,必須等待另一個線程調用notify或者notifyAll方法才能夠喚醒。 |
Timed Waiting(計時等待) | 同waiting狀態,有幾個方法有超時參數,調用他們將進入Timed Waiting狀態。這一狀態 將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數的常用方法有Thread.sleep 、 Object.wait。 |
Teminated(被終止) | 因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡。 |
狀態圖:
等待喚醒機制
Object類中的四個方法:
- wait() : 使線程處於等待狀態,等待著其他的線程喚醒
- wait(long millis) : 使線程處於等待狀態, 等待著其他的線程喚醒/等待時間到達
- notify() : 喚醒其他的單個等待的線程
- notifyAll() : 喚醒其他的所有等待的線程
sleep()與wait()
- sleep:Thread類中的靜態方法,休眠指定的時間,在指定時間後自動喚醒,不回釋放鎖對象
- wait : Object類中的方法,無限等待,等待其他線程的喚醒,必須釋放鎖對象
java之初學線程