Lock鎖以及死鎖的形成與解決

上面學到的synchronized可以幫助我們在多線程環境下用作爲線程安全的同步鎖,接下來我們將會學習一個Lock接口的使用,Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。此實現允許更靈活的結構

可以使用Lock鎖進行具體的鎖定操作類 提供了具體的實現類:ReentrantLock

Lock鎖中的方法:加鎖(lock())並且去釋放鎖(unlock())

例如:

package com.westos.Lock;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class MyLock implements Runnable{

//定義票的變量
private int tickets=7;
//定義Lock接口對象
private  Lock lock=new ReentrantLock();
@Override
public void run() {

while(true) {
//給這個循環語句整體try...catch一下以方便後面的釋放鎖
try {
//void lock()獲取鎖
lock.lock();
if(tickets>0) {
//當每個線程進來時讓它睡眠0.1秒 
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"張票");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//void unlock()釋放鎖。
lock.unlock();
}
}
}
}

 

創建一個測試類:

package com.westos.Lock;
 
public class LockDome {
 
public static void main(String[] args) {

//創建資源類對象
MyLock ml=new MyLock();

//創建Thread類對象
Thread t1=new Thread(ml,"窗口1");
Thread t2=new Thread(ml,"窗口2");
Thread t3=new Thread(ml,"窗口3");

//啓動線程
t1.start();
t2.start();
t3.start();
}
}
 
運行結果:
窗口1正在出售第7張票
窗口3正在出售第6張票
窗口2正在出售第5張票
窗口2正在出售第4張票
窗口1正在出售第3張票
窗口3正在出售第2張票
窗口2正在出售第1張票

 

上面介紹了Lock鎖,下來我們看看死鎖(DieLock)的引入:

package com.westos.DieLock;
 
public class MyThread {
 
//創建兩個鎖對象
public static  Object obj=new Object();
public static  Object obj2=new Object();
}
 
package com.westos.DieLock;
 
public class MyDieLock extends Thread{
 
//聲明一個成員變量
private boolean flag;
 
public MyDieLock(boolean flag) {
super();
this.flag = flag;
}

@Override
public void run() {
if(flag) {
synchronized(MyThread.obj) {
System.out.println("if obj");
synchronized(MyThread.obj2) {
System.out.println("if.obj2");
}
}
}else {
synchronized(MyThread.obj2) {
System.out.println("else obj2");
synchronized(MyThread.obj) {
System.out.println("else obj");
}
}
}
}
}

 

 

package com.westos.DieLock;
 
public class LieLockDome {
 
public static void main(String[] args) {
//創建資源類對象
MyDieLock mdl=new MyDieLock(true);
MyDieLock mdl2=new MyDieLock(false);

//啓動線程
mdl.start();
mdl2.start();
}
}
運行結果:
/*
第一種情況:
if obj
else obj2
 
第二種情況:
else obj2
if obj
 
第三種情況:這是一種理想狀態
else obj2
else obj
if obj
if.obj2
 
 */

分析上述產生死鎖的原因:

當你在run方法中的if...else..語句中假設首先進來的是mdl線程,那麼他將會運行if語句中的代碼,先執行obj鎖,然後輸出“if  obj”當它繼續去執行鎖obj2的時候,此時,mdl2線程也搶佔到CPU進來了,那麼他將會去執行else中的代碼,首先執行了鎖obj2然後輸出了“else  obj2”,然後他想接着去執行鎖obj,去輸出“else  obj”時。卻因爲鎖objif語句中還沒有被釋放掉,無法執行,所以在等待if語句中的鎖obj釋放掉。而同樣if語句中執行完鎖obj後想去執行鎖obj2時,也發現鎖obj2else語句中正在執行沒有被釋放,所以也在等待鎖obj2的釋放,所以二者都在互相等待對方釋放鎖,就這樣形成了死鎖。上面的第三種情況是一種理想狀態,它是等一個線程進入if語句或者else語句中執行鎖後然後又釋放掉鎖,此時另一個進程也進來執行另一個鎖,並且釋放掉它,就這樣不會形成死鎖

 

上面我們瞭解了死鎖的形成,我們可以知道死鎖會影響線程之間的通信,那麼如何能夠解決死鎖這個問題呢?

這是就需要用到java中的生產消費者模式:

這個模式就是相當於一個早餐,生產者需要不斷地去做早餐,做出來的早餐還需要被消費者所消費。但是當生產者沒有生產出數據時,消費者在等待數據或者生產者產生數據,消費者卻沒有接受數據,這樣就會容易造成死鎖,所以我們需要生產者產生數據後去喚醒消費者來接受,當消費者需要數據時,就喚醒生產者來生產收據,這樣就會很好的解決死鎖的問題

例如:

先定義一個學生類並設置需要的屬性:

package com.westos.Thread3;
 
public class Student {
 
//設置學生類屬性
String name;
int age;
boolean flag;
}

 

再定義一個生產者類來生產數據:

package com.westos.Thread3;
 
public class SetThread implements Runnable{
//設置學生類對象
private Student  s;
private int x;
//設置該類的有參構造
public SetThread(Student s) {
super();
this.s = s;
}
@Override
public void run() {
while(true) {
//加入同步鎖,可以解決線程之間的搶佔CPU造成數據重複或者錯位的現象
synchronized(s) {
//如果生產者中沒有數據就需要等待生產者去生產數據
if(s.flag) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生產者在生產數據
if(x%2==0) {
s.name="迪麗熱巴";
s.age=25;
}else {
s.name="井傑";
s.age=23;
}
x++;
//當生產好數據後,就需要喚醒消費者來消費數據
s.flag=true;
s.notify();
}
}
}
}

 

 

再定義一個消費者類來消費數據:

package com.westos.Thread3;
 
/**
 *消費者線程
 */
public class GetThread implements Runnable{
 
//創建學生類對象
private Student s;
//創建該類的有參構造
public GetThread(Student s) {
super();
this.s = s;
}
@Override
public void run() {
while(true) {

//設置同步鎖
synchronized(s) {
//如果消費者還有數據,就需要等待先去消費掉它
if(!s.flag) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name+"="+s.age);
//如果消費者沒有數據了,就需要喚醒生產者去生產數據
s.flag=false;
s.notify();
}

}
}
}

 

在創建一個測試類:

package com.westos.Thread3;
 
public class StudentDome {
 
public static void main(String[] args) {

//創建學生類對象
Student s=new Student();
//創建資源類對象
SetThread st=new SetThread(s);
GetThread gt=new GetThread(s);

//創建Thread類對象
Thread t1=new Thread(st);
Thread t2=new Thread(gt);
//啓動線程
t1.start();
t2.start();
}
}
運行結果:
迪麗熱巴=25
井傑=23
迪麗熱巴=25
井傑=23
迪麗熱巴=25
井傑=23
迪麗熱巴=25
井傑=23
迪麗熱巴=25
...

 

這樣就不會造成線程錯亂和一大片一大片出現線程的現象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章