一、兩種程序運調度方式
1、分時調度
所有線程輪流使用 CPU 的使用權,平均分配每個線程佔用 CPU 的時間。
2、搶佔式調度
優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那麼會隨機選擇一個(線程隨機性),Java使用的爲搶佔式調度。
注:多線程程序並不能提高程序的運行速度,但能夠提高程序運行效率,使得CPU的利用率更高。
爲了保證多線程安全,我們通常會採用加同步鎖的方式:
同步方法:在方法聲明上加上synchronized
public synchronized void method(){
可能會產生線程安全問題的代碼
}
靜態同步方法: 在方法聲明上加上static synchronized
public static synchronized void method(){
可能會產生線程安全問題的代碼
}
同步代碼塊:在需要同步的代碼外面包上一個synchronized,同步代碼塊中的鎖對象可以是任意對象,通常會寫成this(即調用者對象)
synchronized(Object o){
可能會產生線程安全問題的代碼塊
}
同步鎖使用的弊端,即引出今天的主要話題:死鎖
二、死鎖產生的四個充分必要條件:
1.互斥條件:一個資源每次只能被一個進程佔用。
2.不剝奪條件:線程已獲得的資源在末使用完之前不能被其他線程強行剝奪,只有自己使用完畢後才釋放資源。
3.請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
4.循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係
產生死鎖必須同時滿足以下四個條件,只要其中任一條件不成立,死鎖就不會發生。
三、產生死鎖的一個例子
如下圖所示,線程 A 持有資源 2,線程 B 持有資源 1,他們同時都想申請對方的資源,所以這兩個線程就會互相等待而進入死鎖狀態。
具體代碼
package com.weiwei;
/**
* Created by Moody丶Yang
* Date:2019/4/20
*/
public class DeadLock {
private static Object resource1=new Object();
private static Object resource2=new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "線程 1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "線程 2").start();
}
}
輸出:
四、避免死鎖的策略
由上述死鎖產生是需要四個充分必要條件的可知, 只要破壞四個條件中的其中一個就可以避免死鎖了。
1、 加鎖順序(線程按照一定的順序加鎖,當多個線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發生.) 簡單、易行!
2、 加鎖時限(當線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己佔有的鎖)
3、死鎖檢測(給線程設置隨機優先級,發生死鎖時,讓一個(或幾個)線程回退,剩下的線程就像沒發生死鎖一樣繼續保持着它們需要的鎖。)
運用法一解決上述死鎖問題(將線程 2加鎖順序與線程1的一致 )
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "線程 2").start();