Java內容梳理(19)API學習(7)執行緒
目錄:
1、程序和執行緒
2、執行緒的建立
3、執行緒的執行方式和使用場景
4、執行緒的生命週期
5、執行緒優先順序
6、守護執行緒
7、執行緒常用API
8、執行緒安全
9、鎖機制
10、執行緒同步控制(死鎖的介紹)
11、定時器
1、程序和執行緒
什麼是程序?
簡單的說:一個獨立執行的程式對應一個程序,程序與程序之間相對獨立,記憶體資料並不共享
什麼是執行緒?
程序由多個執行緒來組成,執行緒共享程序中的資源
執行緒是程序中的邏輯執行單元,當程序啟動時,會立刻啟動一個主執行緒來驅動整個程式邏輯的執行
2、執行緒的建立
(1)利用Runnable介面建立物件
//建立t1執行緒
Thread t1 = new Thread( new Runnable() {
public void run(){
//寫並行邏輯
}
});
//啟動t1執行緒,讓執行緒進入可執行狀態,等待cpu的選中
t1.start();
(2)利用Thread類來建立物件
//建立t2執行緒
Thread t2 = new Thread(){
public void run(){
//寫並行邏輯
}
};
//啟動t2執行緒,讓執行緒進入可執行狀態,等待cpu的選中
t2.start();
3、執行緒的執行方式和使用場景
執行緒的執行方式:
同時執行:併發執行
cpu時間片:cpu執行執行緒的單位時間
併發原理:
單核cpu會不停的在多個執行緒中挑選一個執行緒來執行,執行一個時間片,當時間片到期後,cou核心將掛起這個執行緒,再去挑
選其它執行緒佔用自己的時間片。時間片的切換頻率快到使用者無法察覺,從而給使用者一種併發的感覺
執行緒的使用場景:當有多個程式邏輯需要併發執行時,我們需要使用執行緒來實現。
4、執行緒的生命週期
在整個生命週期中,執行緒會有不同的狀態,每個狀態的特點不一樣
生命週期:從new開始,到run方法結束
5、執行緒優先順序
我們不能控制CPU選中具體哪個執行緒來執行, 我們可以通過設定執行緒的優先順序來告知CPU優先執行哪個執行緒.CPU在挑選可執行狀態
下的執行緒時優先考慮選中優先順序高的執行緒.
設定執行緒的優先順序,setPriority(int newPriority) 其中newPriority範圍只能是1-10之間的整數
當我們沒有指定優先順序時,預設採用:NORM_PRIORITY ( 5 )
必須要線上程start()呼叫前執行才有效
package thread;
public class Run {
public static void main(String[] args) {
Thread t1 = new Thread( new Runnable() {
@Override
public void run() {
System.out.println("t1執行緒執行了");
}
});
//建立t2執行緒
Thread t2 = new Thread(){
@Override
public void run() {
System.out.println("t2執行緒執行了");
}
};
t1.setPriority(1);
t2.setPriority(5);
t1.start();
t2.start();
}
}
6、守護執行緒
特點:
當程序中普通的執行緒全部執行完畢後,整個程序結束
守護執行緒不影響程序的結束
如:gc執行緒
setDaemon( boolean on )設定當前執行緒物件是否為守護執行緒
必須線上程啟動前呼叫才有效
package thread;
public class Run {
public static void main(String[] args) {
Thread t1 = new Thread( new Runnable() {
@Override
public void run() {
System.out.println("t1執行緒執行了");
}
});
Thread t2 = new Thread(){
@Override
public void run() {
System.out.println("t2執行緒執行了");
}
};
t1.setDaemon(true);
t1.start();
t2.start();
}
}
7、執行緒常用API
Thread.currentThread();獲取當前執行緒物件
getId();獲取執行緒ID
getName();獲取當前執行緒的名稱
sleep(long mills);讓執行緒休眠指定的毫秒數;執行緒會進入阻塞狀態,指定時間過後執行緒重新進入可執行狀態
join();讓當前執行緒等待呼叫join方法的執行緒執行完畢後,再執行當前執行緒,讓本來並行的兩條執行緒變為序列執行
package thread;
public class Run2 {
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
System.out.println("t1執行緒執行");
try {
sleep(3*1000);
System.out.println("當前執行緒阻塞3秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
try {
t1.sleep(2*1000);
System.out.println("t1執行緒阻塞2秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.start();
}
}
package thread;
public class Run3 {
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
System.out.println("t1執行緒執行");
}
};
Thread t2 = new Thread() {
public void run() {
try {
t1.join();//保證了t1執行緒始終會在t2執行緒之前執行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2執行緒執行");
}
};
t2.start();
t1.start();
}
}
8、執行緒安全和鎖機制
典型的執行緒安全問題:髒讀
原因:多個執行緒訪問同一資源時,有某些執行緒對該資源進行了修改,其它執行緒再次讀取資源,會與開始讀取的不一致
鎖機制:避免執行緒安全問題的一種手段,synchronized關鍵字,加鎖。
只有先獲得鎖的執行緒A,才能進入synchronized程式碼塊中去執行程式碼,其它執行緒都在等待執行緒A釋放這個鎖後,再去競爭鎖,然後執
行程式碼。
執行緒A釋放鎖的時機:
1. 執行緒A順利執行完synchronized程式碼塊
2. 執行緒A在執行synchronized程式碼塊時出現異常丟擲導致synchronized程式碼塊異常退出時
3. 呼叫鎖物件的wait方法時
注意:
1、若想多個程式碼塊序列執行,必須要讓這幾個程式碼塊被"同一把鎖"鎖住
public void method(){
synchronized( this ){
//方法體
}
}
等同於
public synchronized method(){
//方法體
}
2、對靜態方法加synchronized鎖的是類
package thread;
public class Run4 {
private static Long count = 1L;
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
/*對count這個臨界資源加鎖,count也就是"鎖物件"*/
synchronized (count) {
count += 1;
System.out.println("t1執行");
System.out.println(count);
}
}
};
Thread t2 = new Thread() {
public void run() {
/*對count這個臨界資源加鎖*/
synchronized (count) {
count += 2;
System.out.println("t2執行");
System.out.println(count);
}
}
};
t1.start();
t2.start();
}
}
舉例:鎖住當前物件
舉例:鎖住其它物件
9、執行緒同步控制(死鎖的介紹)
設計Object類中的三個方法:wait / notify / notifyAll
wait:讓當前執行緒等待(進入阻塞狀態)並釋放其佔用鎖
notify:通知被同個鎖阻塞的執行緒中的某個執行緒恢復可執行狀態,喚醒對應的鎖池中的某個執行緒,進入可執行狀態
notifyAll:喚醒對應鎖池中的全部執行緒
注意:這三個方法必須在synchronized程式碼塊中被使用
死鎖:
什麼是死鎖?
執行緒A等待執行緒B釋放鎖,同時執行緒B也在等待執行緒A釋放鎖,從而形成相互等待的情況,這種情況稱為死鎖
通常情況下死鎖的發生:
多個執行緒需要按照各自的順序來同時使用a鎖和b鎖,但由於這多個執行緒使用a鎖和b鎖的順序不一致,就可能導致死鎖
解決死鎖的思路:
若多個執行緒需要同時使用多個鎖時,我們應該儘量讓這多個執行緒使用這些鎖物件時的順序是一致的.
舉例:生產者與消費者
以生產蛋糕為例應該考慮:
(1)生產速度大於賣出速度
(2)賣出速度大於生產速度
package thread;
import java.util.List;
/**
* 生產者執行緒
*/
public class Maker extends Thread{
/*container表示放蛋糕的貨架*/
private List<String> container;
private int maxSize;
/*由外界提供貨架和貨架的最大裝載量*/
public Maker(List<String> container, int maxSize) {
this.container = container;
this.maxSize = maxSize;
}
public void run() {
while(true) {
/*貨架沒滿,就生產蛋糕,往上放*/
if(container.size() <= maxSize) {
try {
System.out.println("生產蛋糕,放入貨架");
this.sleep(2000);
container.add("蛋糕");
System.out.println("貨架上的蛋糕剩餘:"+container.size());
/*檢測貨架上的蛋糕數,若大於4個,就要去賣蛋糕了;不能等到生產滿了貨架才賣*/
synchronized (container) {
if(container.size() >= 4 ) {
container.notifyAll();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
/*貨架滿了,等待蛋糕賣出;或者是生產速度大於賣出速度*/
synchronized (container) {
try {
System.out.println("貨架滿了,等待蛋糕賣出");
//喚醒消費程序,賣蛋糕
container.notifyAll();
//讓當前生產蛋糕執行緒等待,再釋放其對container的佔用鎖
container.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
package thread;
import java.util.List;
/**
*消費者執行緒
*/
public class Saller extends Thread{
private List<String> container;
public Saller(List<String> container) {
super();
this.container = container;
}
public void run() {
while(true) {
if(container.size() > 0 ) {
try {
System.out.println("賣蛋糕,賣一個,貨架減一個");
Thread.sleep(2000);
container.remove(0);
System.out.println("貨架上的蛋糕剩餘:"+container.size());
/*檢測貨架上的蛋糕數,若小於4個,就要去生產蛋糕了;不能等到賣完了才去生產*/
synchronized (container) {
if(container.size() <= 4 ) {
container.notifyAll();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
/*賣蛋糕賣的快,先賣完的情況*/
synchronized (container) {
try {
System.out.println("蛋糕賣完了,等待生產");
//喚醒生產蛋糕執行緒
container.notifyAll();
//讓賣蛋糕執行緒等待,釋放對container的鎖
container.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
package thread;
import java.util.ArrayList;
import java.util.List;
public class Run5 {
public static void main(String[] args) {
List<String> container = new ArrayList<>();
int maxSize = 15;
Maker m = new Maker(container, maxSize);
Saller s = new Saller(container);
m.start();
s.start();
}
}
10、定時器
它可以週期性的定時執行指定的任務
java.util.Timer類:表示一個計時器(執行者)
schedule(task, 1000):延遲1000毫秒執行task任務(僅執行一次)
schedule(task, 0, 1000):延遲0毫秒執行task任務,每個1000毫秒執行一次這個task任務
cancel():結束計時器
java.util.TimerTask類:表示一個計時器任務(事情)
cancel():結束當前這個任務
package thread;
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
public static void main(String[] args) {
/*建立一個計時器*/
Timer timer = new Timer();
/*建立一個計時器任務:大掃除*/
TimerTask task1 = new TimerTask() {
private int day = 1;
@Override
public void run() {
System.out.println("打掃衛生");
}
};
/*延遲2秒執行task1,並且只執行一次,但是計時器不會關閉*/
//timer.schedule(task1, 2000);
/*每3秒執行一次task1,0表示不會延遲執行*/
timer.schedule(task1, 0, 3000);
/*取消計時器,計時器立即關閉,不會執行任何任務*/
timer.cancel();
}
}