9.6 同步與死鎖
一個多線程的程序如果是通過Runnabl接口實現的,則意味着類中的屬性將被多個線程共享,那麼這樣一來就會造成一種問題,如果這多個線程操作同一資源時就有可能出現資源的同步問題。例如, 賣票程序,如果多個線程同時操作時就有可能出現賣出票爲負數的問題。
9.6.1 問題的產生
範例:通過Runnable接口實現多線程,併產生3個線程對象,同時賣5張票。
class MyThread implements Runnable{
private int ticket = 5;
public void run(){
for (int i = 0; i < 7; i++){
if (ticket > 0){
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("賣票: ticket = " + ticket--);
}
}
}
}
public class SyncDemo01{
public static void main(String args[]){
MyThread my = new MyThread();
new Thread(my, "Thread-A").start();
new Thread(my, "Thread-B").start();
new Thread(my, "Thread-C").start();
}
}
運行結果截圖
賣票: ticket = 2
賣票: ticket = 1
賣票: ticket = 0
<span style="color:#ff0000;">賣票: ticket = -1</span>
-------------------------------------------------
從程序的運行結果中可以發現,程序中加入了延遲操作,所以在運行的最後出現了負數的情況,那麼爲什麼現在會產生這樣的問題呢?
上面程序對於票數的操作步驟如下:
- 判斷票數是否大於0,大於0則表示還有票可以賣
- 如果票數大於0,則將票賣出。
但是,代碼中,在步驟1和步驟2之間加入了延遲操作,那麼一個線程就有可能在還沒有對票數進行減法操作之前,其他線程就已經將票數減少了,這樣一來就會出現票數爲負的情況。
如果想解決這樣的問題,就必須使用同步。
所謂同步就是指多個操作在同一個時間段內只能有一個線程進行,其他線程要等待此線程完成之後纔可以繼續執行,如下圖所示:
9.6.2 使用同步解決問題
解決資源共享的同步操作,可以使用同步代碼塊和同步方法兩種方式完成。
- 同步代碼塊
所謂的代碼塊就是指使用“{}”括起來的一段代碼,根據其位置和聲明的不同,可以分爲普通代碼塊、構造塊、靜態塊3種,如果在代碼塊前面加上synchronized關鍵字,則此代碼塊就稱爲同步代碼塊。 其格式如下
synchronized(同步對象){
需要同步的代碼;
}
*******從上面的格式可以看出,在使用同步代碼塊時必須制定一個同步對象,但一般都將當前對象(this)設置成同步對象!!!*******
範例:使用同步代碼塊解決的同步問題
class MyThread implements Runnable{
private int ticket = 5;
public void run(){
for (int i = 0; i < 7; i++){
synchronized(this){
if (ticket > 0){
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("賣票: ticket = " + ticket--);
}
}
}
}
}
public class SyncDemo01{
public static void main(String args[]){
MyThread my = new MyThread();
new Thread(my, "Thread-A").start();
new Thread(my, "Thread-B").start();
new Thread(my, "Thread-C").start();
}
}
運行結果:
-------------------------------------------------
賣票: ticket = 5
賣票: ticket = 4
賣票: ticket = 3
賣票: ticket = 2
賣票: ticket = 1
-------------------------------------------------
將上面代碼的取值和修改值的操作代碼進行了同步,所以不會再出現賣出票爲負數的情況了。
- 同步方法
除了可以將需要的代碼設置成同步代碼塊外,也可以使用synchronized關鍵字將一個方法聲明成同步方法。其格式如下:
synchronized 方法返回值 方法名稱(參數列表){
方法體;
}
範例: 使用同步方法解決賣票出負數的情況
class MyThread implements Runnable{
private int ticket = 5;
public void run(){
for (int i = 0; i < 7; i++){
this.sale();
}
}
private synchronized void sale(){
if (ticket > 0){
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("賣票:ticket = " + ticket--);
}
}
}
public class SyncDemo02{
public static void main(String args[]){
MyThread my = new MyThread();
new Thread(my, "Thread-A").start();
new Thread(my, "Thread-B").start();
new Thread(my, "Thread-C").start();
}
}
運行結果如下,與上面同步代碼塊實現了完全相同的功能。
-------------------------------------------------
賣票:ticket = 5
賣票:ticket = 4
賣票:ticket = 3
賣票:ticket = 2
賣票:ticket = 1
-------------------------------------------------
- 方法定義的完整格式
訪問權限 {public|default|protected|private} [final] [static] [synchronized] 返回值類型|void 方法名稱(參數類型 參數名稱,…) [throws Exception1, Exception2] {
[return [返回值|返回調用處]]
}
9.6.3 死鎖
同步可以保證資源共享操作的正確性,但是過多同步也會產生問題。例如, 現在張三想要李四的畫,李四想要張三的書;張三對李四說“把你的畫給我,我就給你書”,李四也對張三說了:“把你的書給我,我就給你畫”,此時,張三在等着李四的答覆,而李四也在等着張三的答覆;那麼這樣下去最終結果就是,張三得不到李四的畫,李四也得不到張三的書。這就是死鎖的概念!!
所謂死鎖就是指兩個線程都在等待彼此先完成,造成了程序的停滯,一般程序的死鎖都是在程序運行時出現的。
範例:死鎖
class Zhangsan{
public void say(){
System.out.println(" 張三對李四說:“你給我畫,我就把書給你。”");
}
public void get(){
System.out.println("張三得到畫了!!");
}
}
class Lisi{
public void say(){
System.out.println("李四對張三說:“你給我書,我就把畫給你。”");
}
public void get(){
System.out.println("李四得到書了!!");
}
}
public class ThreadDeadLock implements Runnable{
private static Zhangsan zs = new Zhangsan();
private static Lisi ls = new Lisi();
private boolean flag = false; //標記,用於判斷哪個對象先執行
public void run(){
if(flag){
synchronized (zs){
zs.say();
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized(ls){
zs.get();
}
}
}else{
synchronized(ls){
ls.say();
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized(zs){
ls.get();
}
}
}
}
public static void main(String args[]){
ThreadDeadLock t1 = new ThreadDeadLock();
ThreadDeadLock t2 = new ThreadDeadLock();
t1.flag = true;
t2.flag = false;
new Thread(t1, "Thread-A").start();
new Thread(t2, "Thread-B").start();
}
}
程序運行結果:
-------------------------------------------------
張三對李四說:“你給我畫,我就把書給你。”
李四對張三說:“你給我書,我就把畫給你。”
[以下代碼不再執行,程序進入死鎖狀態]
從程序的運行結果中可以發現,兩個線程都在彼此等着對方的執行完成,這樣,程序就無法向下繼續執行,從而造成了死鎖的現象。
- 關於同步與死鎖, 多個線程共享同一資源時需要進行同步,以保證資源操作的完整性;但是過多的同步就有可能產生死鎖。。。
關於線程的死鎖、互鎖、互斥鎖, 以及它們產生的原因、排錯方法等等要自己查資料看看!!!!!!!!!!!