java 多執行緒 死鎖和valitile關鍵字
死鎖
兩個或者多個執行緒都在等待對方釋放鎖,在寫多執行緒程式碼時要注意避免這種死鎖的發生
發生死鎖後可以在dos命令列輸入jps命令檢視java程序狀況
可以試用jstack -l 程序號 命令檢視當前類的問題
關閉jvm停止死鎖
以上節三個公司賣電影票為例,指出寫問題,這個不是死鎖啊,先看程式碼:
package collection; import java.util.ArrayList; import java.util.Iterator; import java.util.Random; //開啟三個執行緒 public class synchronizedTest06 { public static void main(String[] args) { Ticket tc = new Ticket(); Thread t1 = new Thread(tc, "美團電影"); Thread t2 = new Thread(tc, "淘票票"); Thread t3 = new Thread(tc, "貓眼電影"); t1.start(); t2.start(); t3.start(); } } class Ticket implements Runnable { // 100張電影票 ArrayList<Integer> ticketArr; Random random; public Ticket() { ticketArr = new ArrayList<>(); random = new Random(); for (int i = 0; i < 100; i++) { ticketArr.add(i + 1); } } @Override public synchronized void run() { while (true) { // if (ticketArr.size()<=0){ // break; // } int j = random.nextInt(100) + 1; if (ticketArr.contains(j)) { // 利用迴圈迭代來刪除List中的元素,直接刪除可能會報錯,因為刪除元素後List元素的索引會變化,所以不能只用用索引來刪除 Iterator<Integer> it = ticketArr.iterator(); while (it.hasNext()) { if (it.next().equals(j)) { it.remove(); break; } } System.out.println(Thread.currentThread().getName() + "賣出了第" + j + "張票,還剩餘" + ticketArr.size() + "張票"); } } } }
這段程式碼註釋掉了執行緒結束的條件,執行效果是隻有一個執行緒會打印出所有的電影票,且列印完以後,程式不會停止,其他執行緒在等待這個執行緒釋放鎖,但是這並不是死鎖,因為當前執行緒沒有要執行被別的執行緒鎖住的語句塊或者方法
下面這個示例演示了死鎖:
public class DeadLock { public static void main(String[] args) { Object obj1 = new Object(); Object obj2 = new Object(); Thread t1 = new Thread(new Dead(obj1, obj2)); Thread t2 = new Thread(new Dead(obj2, obj1)); t1.start(); t2.start(); } } class Dead implements Runnable { Object obj1; Object obj2; public Dead(Object obj1, Object obj2) { super(); this.obj1 = obj1; this.obj2 = obj2; } @Override public void run() { synchronized (obj1) { System.out.println(Thread.currentThread().getName() + obj1); synchronized (obj2) { System.out.println(Thread.currentThread().getName() + obj2); } } } }
上面 程式發生死鎖,執行緒1先鎖住obj1再鎖住obj2,執行緒2先鎖住obj2再鎖住obj1.比如執行緒1先執行,鎖住obj1,此時(執行緒2開始執行,鎖住obj2,此時無論哪個執行緒都無法繼續執行,都在等待對方釋放鎖,還有一種情況,執行緒1繼續執行鎖住obj2,然後釋放掉鎖,執行緒2就可以運行了,但這種情況發生的機率非常小)
Java 記憶體模型中的可見性、原子性和非原子性
可見性,是指執行緒之間的可見性,一個執行緒修改的狀態對另一個執行緒是可見的,
原子性:原子具有不可分割性,即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼都不執行
非原子性:不復合原子性的都是非原子性
舉例說明,int a=10;return 語句,這些操作是原子性,可以一次性執行完,規定基本資料型別的賦值是原子性的,
a++ 這個操作是先取出a,再將a+1,是可分割的,所以是非原子性的,非原子性的都會存線上程安全問題,
synchronized關鍵字可以讓多條語句變成原子性的,比如同步方法,同步程式碼塊
volatile關鍵字
使用volitale關鍵字可以保證多個執行緒之間共享變數的可見性,只能修飾變數,不能修飾方法
畫個圖來說明:
使用這個關鍵字之前,比如說執行緒t1在執行一個方法,呼叫了物件的屬性flag,有些環境為了提高效率,將用到的屬性儲存到執行緒的私有記憶體,不再對堆記憶體中的屬性進行訪問,當執行緒t2對這個物件的屬性修改後,執行緒t1用的還是私有記憶體中的flag值,這個時候就會出現執行緒安全問題,試用volitale關鍵字修飾屬性後,共享變數具有可見性,就不會再出現上述問題
例子說明:
package chen_chapter_9;
public class VolitaleTest01 {
public static void main(String[] args) {
Work work = new Work();
Thread t1 = new Thread(work, "執行緒t1");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 在某些環境下,執行緒會將堆記憶體中變數的值儲存到自己的私有記憶體來提高效率,該執行緒不再訪問堆記憶體中的值
// 這時在主執行緒中修改堆記憶體中的值對上述執行緒無影戲
work.setAge(20);
}
}
class Work implements Runnable {
private volatile int age = 9;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void run() {
while (age<10) {
System.out.println(Thread.currentThread().getName() + " : " + getAge());
}
}
}
out:
我的環境沒有出現變數不可見的情況,在這裡將其用volitale修飾是為了方便,不再修改了
在64位機上,以server模式執行,不加volitale關鍵字可能出現上述變數不可見的情況
修改為server的方法,在eclipse程式碼框上,右鍵-Run As-Run Configurations-Arguments-第二行的VM arguments,框中輸入-server就會以伺服器模式執行.
JVM的執行可以分兩種模式:
client:啟動快,執行後效能不如server模式,一般執行時預設是client模式
server:啟動慢,執行後效能比client模式好。
volitale關鍵字不具有原子性