一、課程目标
了解線程安全?
synchronized用法
死鎖
二、什麼是線程安全?
2.1 為什麼有線程安全問題?
當多個線程同時共享,同一個全局變量或靜态變量,做寫的操作時,可能會發生資料沖突問題,也就是線程安全問題。但是做讀操作是不會發生資料沖突問題。
案例:需求現在有100張火車票,有兩個視窗同時搶火車票,請使用多線程模拟搶票效果。
代碼:運作結果:
public class ThreadTrain1 implements Runnable {
// 這是貨票總票數,多個線程會同時共享資源
private int trainCount = 100;
@Override
public void run() {
while (trainCount > 0) {// 循環是指線程不停的去賣票
try {
// 等待100毫秒
Thread.sleep(10);
} catch (InterruptedException e) {
}
sale();
}
}
public void sale() {
if (trainCount > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
ThreadTrain1 threadTrain = new ThreadTrain1(); // 定義 一個執行個體
Thread thread1 = new Thread(threadTrain, "一号視窗");
Thread thread2 = new Thread(threadTrain, "二号視窗");
thread1.start();
thread2.start();
}
}
一号視窗和二号視窗同時出售火車第一張和第七張,部分火車票會重複出售。
結論發現,多個線程共享同一個全局成員變量時,做寫的操作可能會發生資料沖突問題。
三、線程安全解決辦法:
問:如何解決多線程之間線程安全問題?
答:使用多線程之間同步或使用鎖(lock)。
問:為什麼使用線程同步或使用鎖能解決線程安全問題呢?
答:将可能會發生資料沖突問題(線程不安全問題),隻能讓目前一個線程進行執行。代碼執行完成後釋放鎖,然後才能讓其他線程進行執行。這樣的話就可以解決線程不安全問題。
問:什麼是多線程之間同步?
答:當多個線程共享同一個資源時,不會受到其他線程的幹擾。
3.1、使用同步代碼塊
什麼是同步代碼塊?
答:就是将可能會發生線程安全問題的代碼,給包括起來。
synchronized(同一個資料){
可能會發生線程沖突問題
}
代碼樣例:
private Object mutex = new Object();// 自定義多線程同步鎖
public void sale() {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" +
(100 - trainCount + 1) + "張票.");
trainCount--;
}
}
}
3.2、同步函數
什麼是同步函數?
答:在方法上修飾synchronized 稱為同步函數
代碼樣例
public synchronized void sale() {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
同步函數用的是什麼鎖?
答:同步函數使用this鎖。
證明方式: 一個線程使用同步代碼塊(this明鎖),另一個線程使用同步函數。如果兩個線程搶票不能實作同步,那麼會出現資料錯誤。
代碼:
public class ThreadTrain5 implements Runnable {
// 這是貨票總票數,多個線程會同時共享資源
private int trainCount = 100;
public boolean flag = true;
private Object mutex = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
}
} else {
while (true) {
sale();
}
}
}
public synchronized void sale() {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
}
public class ThreadDemo5 {
public static void main(String[] args) throws InterruptedException {
ThreadTrain5 threadTrain = new ThreadTrain5(); // 定義 一個執行個體
Thread thread1 = new Thread(threadTrain, "一号視窗");
Thread thread2 = new Thread(threadTrain, "二号視窗");
thread1.start();
Thread.sleep(40);
threadTrain.flag = false;
thread2.start();
}
}
3.3、靜态同步函數
答:什麼是靜态同步函數?
方法上加上static關鍵字,使用synchronized 關鍵字修飾 或者使用類.class檔案。
靜态的同步函數使用的鎖是該函數所屬位元組碼檔案對象
可以用 getClass方法擷取,也可以用目前類名.class 表示。
代碼樣例:
synchronized (ThreadTrain.class){
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
總結:
synchronized 修飾方法使用鎖是目前this鎖。
synchronized 修飾靜态方法使用鎖是目前類的位元組碼檔案
四、多線程死鎖
4.1、什麼是多線程死鎖?
答:同步中嵌套同步,導緻鎖無法釋放
代碼:
class ThreadTrain6 implements Runnable {
// 這是貨票總票數,多個線程會同時共享資源
private int trainCount = 100;
public boolean flag = true;
private Object mutex = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized (mutex) {
// 鎖(同步代碼塊)在什麼時候釋放? 代碼執行完, 自動釋放鎖.
// 如果flag為true 先拿到 obj鎖,在拿到this 鎖、 才能執行。
// 如果flag為false先拿到this,在拿到obj鎖,才能執行。
// 死鎖解決辦法:不要在同步中嵌套同步。
sale();
}
}
} else {
while (true) {
sale();
}
}
}
public synchronized void sale() {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
}
}
public class DeadlockThread {
public static void main(String[] args) throws InterruptedException {
ThreadTrain6 threadTrain = new ThreadTrain6(); // 定義 一個執行個體
Thread thread1 = new Thread(threadTrain, "一号視窗");
Thread thread2 = new Thread(threadTrain, "二号視窗");
thread1.start();
Thread.sleep(40);
threadTrain.flag = false;
thread2.start();
}
}
五、練習題
5.1、設計4個線程,其中兩個線程每次對j增加1,另外兩個線程對j每次減少1。寫出程式。
六、面試題
問:什麼是多線程安全?
答:當多個線程同時共享同一個全局變量或靜态變量,做寫的操作時,可能會發生資料沖突問題,也就是線程安全問題。做讀操作是不會發生資料沖突問題。
問:如何解決多線程之間線程安全問題?
答:使用多線程之間同步或使用鎖(lock)。
問:為什麼使用線程同步或使用鎖能解決線程安全問題呢?
答:将可能會發生資料沖突問題(線程不安全問題),隻能讓目前一個線程進行執行。被包裹的代碼執行完成後釋放鎖,讓後才能讓其他線程進行執行。這樣的話就可以解決線程不安全問題。
問:什麼是多線程之間同步?
答:當多個線程共享同一個資源,不會受到其他線程的幹擾。
問:什麼是同步代碼塊?
答:就是将可能會發生線程安全問題的代碼,給包括起來。隻能讓目前一個線程進行執行,被包裹的代碼執行完成之後才能釋放所,讓後才能讓其他線程進行執行。
問:多線程同步的分類?
1.使用同步代碼塊?
synchronized(同一個資料){
可能會發生線程沖突問題
}
private Object mutex = new Object();// 自定義多線程同步鎖
public void sale() {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
}
2.使用同步函數
在方法上修飾synchronized 稱為同步函數
public synchronized void sale() {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
3.靜态同步函數
方法上加上static關鍵字,使用synchronized 關鍵字修飾 為靜态同步函數
靜态的同步函數使用的鎖是 該函數所屬位元組碼檔案對象
問:同步代碼塊與同步函數差別?
答:
同步代碼使用自定鎖(明鎖)
同步函數使用this鎖
問:同步函數與靜态同步函數差別?
注意:有些面試會這樣問:例如現在一個靜态方法和一個非靜态靜态怎麼實作同步?
答:
同步函數使用this鎖
靜态同步函數使用位元組碼檔案,也就是類.class
問:什麼是多線程死鎖?
答: