目錄:
- 線程安全
- 線程同步
- 同步代碼塊
- 同步方法
- Lock鎖
- 線程狀態圖
- sleep睡眠
- 等待和喚醒
1. 線程安全
- 如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程序每次運行結果和單線程運行的結果是一樣
的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
我們通過一個案例,演示線程的安全問題:
電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “葫蘆娃大戰奧特曼”,本次電影的座位共100個(本 場電影只能賣100張票)。我們來模擬電影院的售票窗口,實現多個窗口同時賣 “葫蘆娃大戰奧特曼”這場電影票(多個窗口一起賣這100張票) 需要窗口,採用線程對象來模擬;需要票,Runnable接口子類來模擬
模擬票:
public class Ticket implements Runnable {
private int ticket = 100;
/*
* 執行賣票操作
* */
@Override
public void run() {
//每個窗口賣票的操作
//窗口 永遠開啓
while (true) {
if (ticket > 0) {//有票 可以賣
//出票操作
//使用sleep模擬一下出票時間
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//獲取當前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + ticket--);
}
}
}
}
測試類:
public class Demo {
public static void main(String[] args) {
//創建線程任務對象
Ticket ticket = new Ticket();
//創建三個窗口對象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
//同時賣票
t1.start();
t2.start();
t3.start();
}
}
效果圖:
- 相同的票數,比如5這張票被賣了3回。
- 不存在的票,比如0票與-1票,是不存在的。
- 這種問題,幾個窗口(線程)票數不同步了,這種問題稱爲線程不安全。
2. 線程同步:
線程同步是爲了解決線程安全問題。
Java中提供了同步機制(synchronized)來解決。
根據案例簡述:
窗口1線程進入操作的時候,窗口2和窗口3線程只能在外等着,窗口1操作結束,窗口1和窗口2和窗口3纔有機會進入代碼去執行。也就是說在某個線程修改共享資源的時候,其他線程不能修改該資源,等待修改完畢同步之後,才能去搶奪CPU資源,完成對應的操作,保證了數據的同步性,解決了線程不安全的現象。
有三種方式完成同步操作:
- 同步代碼塊。
- 同步方法。
- 鎖機制。
3.同步代碼塊
同步代碼塊: synchronized 關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。
格式:
synchronized(同步鎖){
需要同步操作的代碼
}
對象的同步鎖只是一個概念,可以想象爲在對象上標記了一個鎖
- 鎖對象 可以是任意類型。
- 多個線程對象 要使用同一把鎖。
代碼演示:
public class Ticket implements Runnable {
private int ticket = 100;
Object lock = new Object();
// 執行賣票操作
@Override
public void run() {
//每個窗口賣票的操作
//窗口 永遠開啓
while (true) {
synchronized (lock) {
if (ticket > 0) {//有票 可以賣
//出票操作
//使用sleep模擬一下出票時間
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//獲取當前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + ticket--);
}
}
}
}
}
4.同步方法
- 同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外等着
格式:
public synchronized void method(){
可能會產生線程安全問題的代碼
}
同步鎖是誰?
- 對於非static方法,同步鎖就是this。
- 對於static方法,我們使用當前方法所在類的字節碼對象(類名.class)。
使用同步方法代碼如下:
public class Ticket implements Runnable{
private int ticket = 100;
/*
* 執行賣票操作
*/
@Override
public void run() {
//每個窗口賣票的操作
//窗口 永遠開啓
while(true){
sellTicket();
}
}
/*
* 鎖對象 是 誰調用這個方法 就是誰
* 隱含 鎖對象 就是 this
*
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以賣
//出票操作
//使用sleep模擬一下出票時間
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//獲取當前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket--);
}
}
}
5.Lock鎖
Lock介紹:
Lock更加靈活,它裏面有相應的方法:
- public void lock() :加同步鎖。
- public void unlock() :釋放同步鎖。
Lock實現類:
ReentrantLock
Lock使用標準方式
l.lock(); 獲得鎖
try {
操作共享資源的代碼
} finally {
l.unlock();
}
public class SaleTicketRunnable implements Runnable {
int ticketNum = 100; // 表示100張票
Lock l = new ReentrantLock();
@Override
public void run() {
// 2.重寫run方法,在run方法中實現這個賣票邏輯
String name = Thread.currentThread().getName();
while (true) {
// 如果票數大於0,就賣一張
l.lock(); // 獲得鎖
try {
if (ticketNum > 0) {
try { // 模擬賣票要花的時間
Thread.sleep(10);
} catch (InterruptedException e) {}
ticketNum--;
System.out.println(name + "賣了一張票,剩餘: " + ticketNum);
} else {
// 沒有票了,結束循環,停止了賣票
break;
}
} finally {
l.unlock(); // 釋放鎖
}
}
}
}
6. 線程狀態:
線程狀態圖:
7. 睡眠sleep方法
public static void sleep(long time) 讓當前線程進入到睡眠狀態,到毫秒後自動醒來繼續執行
public class Test{
public static void main(String[] args){
for(int i = 1;i<=5;i++){
Thread.sleep(1000);//主線程執行到sleep方法會休眠1秒後再繼續執行。
System.out.println(i)
}
}
}
8. 等待和喚醒
講解:
- public void wait() : 讓當前線程進入到等待狀態 此方法必須鎖對象調用.
- 等待和喚醒,通常是兩個線程之間的事情,一個線程等待,另外一個線程負責喚醒
Object類的方法:
- void wait() 導致當前線程等待
- void notify() 喚醒正在等待的單個線程
注意: wait和notify必須在同步代碼塊中,使用鎖對象來調用
兩道面試題:
1.爲什麼wait和notify方法放在Object
wait和notify使用鎖對象來調用,任何對象都可以作爲鎖,所以就放在Object類中
2.sleep和wait的區別
1.sleep睡眠的時候不會釋放鎖
2.wait等待的時候會釋放鎖
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
// obj.wait(); // IllegalMonitorStateException: 非法監視狀態異常
new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
System.out.println("wait前");
obj.wait();
System.out.println("wait後");
} catch (InterruptedException e) {}
}
}
}).start();
Thread.sleep(3000);
new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
System.out.println("準備喚醒");
obj.notify();
}
}
}).start();
}