併發與並行
併發:兩個或多個事件(線程)在同一時間段內發生(一個Cpu交替執行)
並行:兩個或多個事件(線程)在同一時刻發生
線程與進程
進程:是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創建、運行到消亡的過程。
進入到內存的程序就是進程
線程:能獨立運行的基本單位,也是獨立調度和分派的基本單位, 不具備獨立的內存空間
進程和線程的區別
- 地址空間:進程是資源分配的基本單位,線程與資源分配無關,它屬於某個進程,與其它線程一起共享此進程的地址空間
- 切換和調度:線程上下文切換比進程上下文切換要快得多
- 在多線程OS中,進程不是一個可執行的實體。
線程調度
- 分時調度:所有線程輪流使用cpu的使用權,平均分配每個線程使用時間
- 搶佔式調度:優先讓優先級高的線程使用CPU,如果優先級相同,則隨機分配,Java則是搶佔式調度
主線程:執行main方法的線程
main方法執行過程:JVM執行main方法,首先將main方法放入棧內存中,開闢一條main通向CPU的路
徑,這個路徑就叫做main(主)線程。
創建多線程
第一種方式:
1、創建一個Thread的子類
2、重寫Thread的run() 方法,run中寫的是新線程執行的任務
3、創建一個Thread的子類對象
4、main中調用該對象的start方法
第二種方式:
1、創建一個實現類實現Runnable接口,實現類中實現run()方法,設置線程任務
2、創建一個實現類對象
3、創建一個thread對象,構造方法中傳遞Runnable實現類對象名稱
4、利用Thread對象調用start()方法,開啓新線程執行run()方法
(線程多次啓動是違法的,線程結束執行結束後,不能再重新啓動)
兩種方法的區別(Runnable的優點)
1、Java不允許多繼承,使用Runnable實現後,還可以繼承其它類
2、方便資源共享(共享一個實例對象),將設置線程任務和開啓新線程進行了分離(解耦),增強了程序的可擴展性,
public class SnapUpThread implements Runnable {
private int ticket = 100;
static int i = 0;
String name = "用戶";
@Override
public void run() { //設置線程任務
i++;
int nameNumber = i;
while(true){
if (ticket <= 0) {
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + nameNumber + "搶到票:" + ticket--);
}
}
}
public static void main(String[] args) {
SnapUpThread runnable = new SnapUpThread();
new Thread(runnable).start(); //開啓新線程
new Thread(runnable).start(); //開啓新線程
}
結果:
多線程的原理
當JVM執行main方法時,開闢了一條從main方法通向CPU的路徑(線程),當執行到Thread th = new newThread();時,由再開闢一條從th通向CPU的路徑,執行到start()方法時便開始執行th線程的run方法。
對於CPU而言,便有了選擇權,隨機選擇線程執行,這就有了程序的隨機打印結果。同樣也可以認爲,兩個線程同時搶奪CPU的執行權(執行時間)。
Thread常用方法
- 獲取線程名稱兩種方式:通過對象名來獲取線程名稱(對象.getName()),或者通過Thread的靜態方法獲取當前線程Thread.currentThread().getName()
- 設置線程名稱兩種方式:通過對象名來獲取線程名稱(對象.setName()),或者在新線程中寫一個帶參name構造方法,調用父類構造,並傳入name參數
- 暫停:Thread.sleep(xxx毫秒);使當前線程暫停xxx秒
線程安全問題
解決辦法
在賣票時(操作共享數據),即使失去cpu控制權,也不允許其它線程來操作共享數據。
當一個線程1搶到cpu使用權後,執行到synchronized語句時,獲得了唯一的鎖對象,因此就算失去cpu使用權,別的線程因爲沒有得到鎖對象,會一直在synchronized語句阻塞,直到線程1釋放出鎖對象。
1、同步代碼塊實現
synchronized(鎖對象){
//操作共享數據的語句
}
注意:
- 鎖對象可以是任意類型對象
- 多個線程中的鎖對象必須是同一個
- 靜態方法中的鎖對象比較特殊,是本類的class屬性-->class文件對象
2、同步方法實現
把操作共享數據的語句封裝成一個方法,方法修飾符後增加synchronized
(同步方法的鎖對象其實就是創建的Runnable實現類對象,所以在第一個方法中也可以使用this作爲鎖對象傳入)
public synchronized int payTicket(){
if (ticket <= 0) {
return 0;
}
System.out.println(name + nameNumber + "搶到票:" + ticket);
ticket--;
return 1;
}
int a = 1;
while(a == 1){
a = payTicket();
}
3、使用Java.util.concurrent.locks.ReentrantLock
1、創建ReentrantLock rt = new ReentrantLock();
2、在操作共享數據的語句前加鎖:rt.lock();
3、在操作完共享數據的語句後放掉鎖:rt.unlock();