線程的創(chuàng)建
創(chuàng)建并開啟一個新的線程
第一種方法
// 創(chuàng)建線程
Thread thread = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("新線程任務(wù)-----------");
}
});
thread.setName("lwy");
// 開啟線程
thread.start();
Thread調(diào)用start方法之后,內(nèi)部會調(diào)用run方法。
注意: 直接調(diào)用run方法并不能開啟新線程,只是在當(dāng)前線程執(zhí)行run里面的任務(wù)而已。 調(diào)用start方法才能開啟新線程(start內(nèi)部有一個native的start0方法,會向內(nèi)核申請開啟新線程)
第二種方法: 創(chuàng)建一個線程類(繼承自Thread)
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("自己的線程類");
}
}
Thread thread = new MyThread();
thread.start();
多線程的內(nèi)存布局
PC寄存器:每個線程都有自己的pc寄存器
java虛擬機(jī)棧:每個線程都有自己的java虛擬機(jī)棧
堆(Heap):多個線程共享堆
方法區(qū): 多個線程共享方法區(qū)
本地方法棧: 每個線程都有自己的本地方法棧
線程的狀態(tài)
java中線程一共有6種狀態(tài)??梢酝ㄟ^getState方法獲取
public enum State {
// 新建(還未啟動)
NEW,
// 可運行狀態(tài)(正在JVM中運行?;蛘哒诘却僮飨到y(tǒng)的其他資源(比如處理器))
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
NEW: 新建(還未啟動
RUNNABLE : 可運行狀態(tài)(正在JVM中運行。或者正在等待操作系統(tǒng)的其他資源(比如處理器))
BLOCKED: 阻塞狀態(tài),正在等待監(jiān)視器鎖(內(nèi)部鎖)
WAITING: 等待狀態(tài),在等待另一個線程
TIMED_WAITING:定時等待狀態(tài),
TERMINATED: 終止?fàn)顟B(tài),已經(jīng)執(zhí)行完畢
BLOCK跟WATING的區(qū)別:
一個線程如果正在執(zhí)行任務(wù),會消耗CPU時間片。BLOCK狀態(tài)是等待鎖,
類似于一直執(zhí)行while(鎖沒有被釋放);,會消耗時間片。而WAITING狀態(tài)就是等待其他線程,處于休眠,CPU不會分配時間片,也不會執(zhí)行其他代碼
線程的方法
sleep、interrupt
可以通過Thread.sleep方法來暫停線程,進(jìn)入WATING狀態(tài)。
在暫停期間,若調(diào)用線程對象的interrupt方法中斷線程,會拋出java.lang.InterruptedExpection異常
Thread thread = new Thread(() -> {
System.out.println(1);
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(2);
});
thread.start();
try {
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println(3);
thread.interrupt();
打印:
1
3
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at Thread.Main.lambda$0(Main.java:12)
at java.base/java.lang.Thread.run(Thread.java:835)
2
join、isAlive
A.join: 等線程A執(zhí)行完畢之后,當(dāng)前線程再繼續(xù)執(zhí)行任務(wù)。可以傳參制定最長等待時間
Thread thread = new Thread(()-> {
System.out.println(1);
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(2);
});
thread.start();
System.out.println(3);
打印
3
1
2
加入等待之后:
Thread thread = new Thread(()-> {
System.out.println(1);
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(2);
});
thread.start();
try {
// 等待線程thread執(zhí)行完畢之后再往下執(zhí)行
thread.join();
} catch (Exception e) {
}
System.out.println(3);
打印
1
2
3
A.isAlive: 查看線程A是否活著
線程安全問題
多個線程可能會共享(訪問)同一個資源,比如訪問同一個對象,同一個變量,同一個文件。當(dāng)多個線程訪問同一塊資源是,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題,成為線程安全問題
什么時候下會出現(xiàn)線程安全問題?
- 多個線程共享同一個資源,且至少一個線程正在進(jìn)行寫操作
線程同步
- 可以使用線程同步技術(shù)來解決線程安全問題
- 同步語句(Synchronized Statement)
- 同步方法(Synchronized Method)
線程同步 - 同步語句
public boolean saleTicket() {
synchronized (this) {
if (tickets < 0)
return false;
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name + "買了一張票, 還剩" + tickets + "張");
return tickets > 0;
}
}
-
synchronized(obj)的原理
- 每個對象都有一個與他相關(guān)的內(nèi)部鎖(intrinsic lock),或者叫做監(jiān)視器鎖(monitor lock)
- 第一個執(zhí)行到同步語句的線程會獲得
obj的內(nèi)部鎖,在執(zhí)行同步語句結(jié)束后會釋放該鎖 - 只要一個線程持有了內(nèi)部鎖,那么其他線程在同一時刻無法再獲得該鎖
- 當(dāng)他們試圖獲取此鎖時,會進(jìn)入
BLOCKED狀態(tài)
線程同步 - 同步方法
public synchronized boolean saleTicket() {
if (tickets < 0)
return false;
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name + "買了一張票, 還剩" + tickets + "張");
return tickets > 0;
}
synchronized不能修飾構(gòu)造方法修飾實例方法時跟同步語句是等價的
靜態(tài)方法的話等價于
synchronized (Class對象)
public synchronized static void test() {
}
public static void test1() {
// Station.class: 類對象 每一個類都只有一個類對象
synchronized (Station.class) {
}
}
死鎖(Deadlock)
什么是死鎖?
兩個或多個線程永遠(yuǎn)阻塞,相互等待對方的鎖
new Thread(() -> {
synchronized ("1") {
System.out.println("1");
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
synchronized ("2") {
System.out.println("1 - 2");
}
}
});
new Thread(() -> {
synchronized ("2") {
System.out.println("2");
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
synchronized ("1") {
System.out.println("2 - 1");
}
}
});
注:同步語句的obj這里傳的是字面量,由于SCP的存在,同一字面量就是同一個對象
線程間通信
可以使用Object.wait,Object.notify,Object.notifyAll方法實現(xiàn)線程間通信
若想在線程A中陳工調(diào)用obj.wait,obj,notify,obj.notifyAll方法,線程A必須要持有obj的內(nèi)部鎖
obj.wait:釋放obj的內(nèi)部鎖,當(dāng)前線程進(jìn)入WATING或TIMED_WAITING狀態(tài)
obj.notifyAll:喚醒所有因為obj.wait進(jìn)入WATING或TIMED_WAITING狀態(tài)的線程
obj.notify:隨機(jī)喚醒一個因為obj.wait進(jìn)入WATING或TIMED_WAITING狀態(tài)的線程
注意:
- 調(diào)用
wait和notify,notifyAll的obj是同一個obj- 調(diào)用
wait和notify,notifyAll的線程必須持有obj的內(nèi)部鎖
可重入鎖(ReentrantLock)
可重入鎖具有跟同步語句,同步方法一樣的一些基本功能,但功能更加強(qiáng)大
什么是可重入?
同一個線程可以重復(fù)獲取同一個鎖。
其他地方叫做遞歸鎖
private ReentrantLock lock = new ReentrantLock();
public boolean saleTicket() {
try {
// lock():必須獲得此鎖,如果鎖被其他線程獲取 將一直等待直到獲得此鎖
lock.lock();
if (tickets < 0)
return false;
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name + "買了一張票, 還剩" + tickets + "張");
return tickets > 0;
} finally {
lock.unlock();
}
}
-
ReentrantLock.lock:獲得此鎖,- 如果此鎖沒有被另一個線程持有,則將鎖的持有計數(shù)設(shè)為1.并且此方法立即返回。
- 如果當(dāng)前線程已經(jīng)持有此鎖,則將此鎖的持有計數(shù)加一,并且此方法立即返回。
- 如果此鎖被另一個線程持有,那么在獲得此鎖之前,此線程將一直處于休眠狀態(tài),直到獲得此鎖。此時鎖的持有計數(shù)被設(shè)為1(雖然被設(shè)為1,但是此線程并沒有持有該鎖,而是在等待獲取該鎖)。
- 所以,調(diào)用了幾次lock方法,對應(yīng)的就要有幾次的unlock方法
-
ReentrantLock.tryLock: 僅在鎖沒有被其他線程持有的情況下,才會獲得此鎖- 如果此鎖沒有被其他線程持有,則將鎖的持有計數(shù)設(shè)為1,并且立即返回
- 如果當(dāng)前線程已經(jīng)持有此鎖,則將鎖的持有計數(shù)加一,并且立即返回
- 如果鎖被另一個線程持有,此方法立即返回false
public boolean saleTicket() {
boolean flag = false;
try {
flag = lock.tryLock();
if (tickets < 0)
return false;
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name + "買了一張票, 還剩" + tickets + "張");
return tickets > 0;
} finally {
if (flag) {
lock.unlock();
}
}
}
-
ReentrantLock.unlock:嘗試釋放此鎖- 如果當(dāng)前線程持有此鎖,則將持有計數(shù)減一
- 如果持有計數(shù)為0,釋放此鎖
- 如果當(dāng)前線程沒有持有此鎖,則拋出異常
ReentrantLock.isLocked:查看此鎖是否被任意線程持有
其實synchronized也是可重入的
public static void main(String[] args) {
synchronized("1") {
synchronized("1") {
System.out.println("123456");
}
}
}
// 打?。?23456
假設(shè)
synchronized不是可重入鎖,那么在第一個synchronized中,獲得了"1"的內(nèi)部鎖,在第二個synchronized中會發(fā)現(xiàn)“1”的內(nèi)部鎖已經(jīng)被持有,然后會等待,但是“1”本身就是被當(dāng)前的線程持有,所以打印語句永遠(yuǎn)不會被執(zhí)行。
但是現(xiàn)在打印了,說明synchronized是可重入鎖,可以多次獲得該鎖
synchronized在不同語言實現(xiàn)不同,有可能在其他語言就不是可重入鎖(遞歸鎖),再像上面那樣寫的話可能就會出錯
線程池
線程對象占用大量的內(nèi)存,在大型項目中,頻繁的創(chuàng)建和銷毀線程對象將產(chǎn)生大量的內(nèi)存管理開銷。
使用線程池可以最大程度的減少線程創(chuàng)建、銷毀帶來的開銷
線程池由工作線程(Worker Thread)組成
普通線程:執(zhí)行完一個工作之后,生命周期就結(jié)束了
-
工作線程:可以執(zhí)行多個任務(wù)(沒有工作時就在等待,有任務(wù)了就開始干活)
- 先將任務(wù)添加到隊列(Queue)中,再從隊列中取出任務(wù)提交到池中
- 常用的線程池類型是固定線程池(Fixed Thread Pool)
- 具有固定適量的正在運行的線程
// 創(chuàng)建線程池
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.execute(() -> {
System.out.println(11 + "_" + Thread.currentThread().getName());
});
pool.execute(() -> {
System.out.println(22 + "_" + Thread.currentThread().getName());
});
pool.execute(() -> {
System.out.println(33 + "_" + Thread.currentThread().getName());
});
pool.execute(() -> {
System.out.println(44 + "_" + Thread.currentThread().getName());
});
// 關(guān)閉線程池
// pool.shutdown();
/*
* 11_pool-1-thread-1
* 22_pool-1-thread-2
* 33_pool-1-thread-3
* 44_pool-1-thread-4
*/