線程的創(chuàng)建
創(chuàng)建并開啟一個新的線程
第一種方法
// 創(chuàng)建線程
Thread thread = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("新線程任務-----------");
}
});
thread.setName("lwy");
// 開啟線程
thread.start();
Thread
調用start
方法之后茶鹃,內部會調用run
方法泊藕。
注意: 直接調用run方法并不能開啟新線程绘趋,只是在當前線程執(zhí)行run里面的任務而已橄务。 調用start
方法才能開啟新線程(start內部有一個native
的start0方法幔托,會向內核申請開啟新線程)
第二種方法: 創(chuàng)建一個線程類(繼承自Thread)
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("自己的線程類");
}
}
Thread thread = new MyThread();
thread.start();
多線程的內存布局
PC寄存器:每個線程都有自己的pc寄存器
java虛擬機棧:每個線程都有自己的java虛擬機棧
堆(Heap):多個線程共享堆
方法區(qū): 多個線程共享方法區(qū)
本地方法棧: 每個線程都有自己的本地方法棧
線程的狀態(tài)
java中線程一共有6種狀態(tài)》渑玻可以通過getState
方法獲取
public enum State {
// 新建(還未啟動)
NEW,
// 可運行狀態(tài)(正在JVM中運行重挑∑刃ぃ或者正在等待操作系統(tǒng)的其他資源(比如處理器))
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
NEW: 新建(還未啟動
RUNNABLE : 可運行狀態(tài)(正在JVM中運行≡艹郏或者正在等待操作系統(tǒng)的其他資源(比如處理器))
BLOCKED: 阻塞狀態(tài)蟆湖,正在等待監(jiān)視器鎖(內部鎖)
WAITING: 等待狀態(tài),在等待另一個線程
TIMED_WAITING:定時等待狀態(tài)玻粪,
TERMINATED: 終止狀態(tài)隅津,已經執(zhí)行完畢
BLOCK跟WATING的區(qū)別:
一個線程如果正在執(zhí)行任務,會消耗CPU時間片劲室。BLOCK狀態(tài)是等待鎖伦仍,
類似于一直執(zhí)行while(鎖沒有被釋放);
很洋,會消耗時間片充蓝。而WAITING狀態(tài)就是等待其他線程,處于休眠喉磁,CPU不會分配時間片谓苟,也不會執(zhí)行其他代碼
線程的方法
sleep、interrupt
可以通過Thread.sleep
方法來暫停線程协怒,進入WATING
狀態(tài)涝焙。
在暫停期間,若調用線程對象的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í)行完畢之后妖滔,當前線程再繼續(xù)執(zhí)行任務隧哮。可以傳參制定最長等待時間
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是否活著
線程安全問題
多個線程可能會共享(訪問)同一個資源座舍,比如訪問同一個對象沮翔,同一個變量,同一個文件簸州。當多個線程訪問同一塊資源是鉴竭,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題,成為線程安全問題
什么時候下會出現(xiàn)線程安全問題岸浑?
- 多個線程共享同一個資源搏存,且至少一個線程正在進行寫操作
線程同步
- 可以使用線程同步技術來解決線程安全問題
- 同步語句(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)的原理
- 每個對象都有一個與他相關的內部鎖(intrinsic lock),或者叫做監(jiān)視器鎖(monitor lock)
- 第一個執(zhí)行到同步語句的線程會獲得
obj
的內部鎖矢洲,在執(zhí)行同步語句結束后會釋放該鎖 - 只要一個線程持有了內部鎖璧眠,那么其他線程在同一時刻無法再獲得該鎖
- 當他們試圖獲取此鎖時,會進入
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
不能修飾構造方法修飾實例方法時跟同步語句是等價的
靜態(tài)方法的話等價于
synchronized (Class對象)
public synchronized static void test() {
}
public static void test1() {
// Station.class: 類對象 每一個類都只有一個類對象
synchronized (Station.class) {
}
}
死鎖(Deadlock)
什么是死鎖责静?
兩個或多個線程永遠阻塞袁滥,相互等待對方的鎖
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中陳工調用obj.wait
,obj,notify
,obj.notifyAll
方法,線程A必須要持有obj的內部鎖
obj.wait
:釋放obj的內部鎖腰鬼,當前線程進入WATING或TIMED_WAITING狀態(tài)
obj.notifyAll
:喚醒所有因為obj.wait
進入WATING或TIMED_WAITING狀態(tài)的線程
obj.notify
:隨機喚醒一個因為obj.wait
進入WATING或TIMED_WAITING狀態(tài)的線程
注意:
- 調用
wait
和notify
,notifyAll
的obj是同一個obj- 調用
wait
和notify
,notifyAll
的線程必須持有obj的內部鎖
可重入鎖(ReentrantLock)
可重入鎖具有跟同步語句嵌赠,同步方法一樣的一些基本功能,但功能更加強大
什么是可重入熄赡?
同一個線程可以重復獲取同一個鎖姜挺。
其他地方叫做遞歸鎖
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ù)設為1.并且此方法立即返回。
- 如果當前線程已經持有此鎖拧篮,則將此鎖的持有計數(shù)加一词渤,并且此方法立即返回。
- 如果此鎖被另一個線程持有他托,那么在獲得此鎖之前掖肋,此線程將一直處于休眠狀態(tài)仆葡,直到獲得此鎖赏参。此時鎖的持有計數(shù)被設為1(雖然被設為1,但是此線程并沒有持有該鎖沿盅,而是在等待獲取該鎖)把篓。
- 所以,調用了幾次lock方法腰涧,對應的就要有幾次的unlock方法
-
ReentrantLock.tryLock
: 僅在鎖沒有被其他線程持有的情況下韧掩,才會獲得此鎖- 如果此鎖沒有被其他線程持有,則將鎖的持有計數(shù)設為1窖铡,并且立即返回
- 如果當前線程已經持有此鎖疗锐,則將鎖的持有計數(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
:嘗試釋放此鎖- 如果當前線程持有此鎖,則將持有計數(shù)減一
- 如果持有計數(shù)為0箍铲,釋放此鎖
- 如果當前線程沒有持有此鎖雇卷,則拋出異常
ReentrantLock.isLocked
:查看此鎖是否被任意線程持有
其實synchronized
也是可重入的
public static void main(String[] args) {
synchronized("1") {
synchronized("1") {
System.out.println("123456");
}
}
}
// 打印:123456
假設
synchronized
不是可重入鎖,那么在第一個synchronized
中关划,獲得了"1"的內部鎖小染,在第二個synchronized
中會發(fā)現(xiàn)“1”的內部鎖已經被持有,然后會等待贮折,但是“1”本身就是被當前的線程持有裤翩,所以打印語句永遠不會被執(zhí)行。
但是現(xiàn)在打印了调榄,說明synchronized
是可重入鎖岛都,可以多次獲得該鎖
synchronized
在不同語言實現(xiàn)不同,有可能在其他語言就不是可重入鎖(遞歸鎖)振峻,再像上面那樣寫的話可能就會出錯
線程池
線程對象占用大量的內存臼疫,在大型項目中,頻繁的創(chuàng)建和銷毀線程對象將產生大量的內存管理開銷扣孟。
使用線程池可以最大程度的減少線程創(chuàng)建烫堤、銷毀帶來的開銷
線程池由工作線程(Worker Thread)組成
普通線程:執(zhí)行完一個工作之后,生命周期就結束了
-
工作線程:可以執(zhí)行多個任務(沒有工作時就在等待凤价,有任務了就開始干活)
- 先將任務添加到隊列(Queue)中鸽斟,再從隊列中取出任務提交到池中
- 常用的線程池類型是固定線程池(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());
});
// 關閉線程池
// pool.shutdown();
/*
* 11_pool-1-thread-1
* 22_pool-1-thread-2
* 33_pool-1-thread-3
* 44_pool-1-thread-4
*/