今天接着昨天的線程再說一說線程的同步問題吧,那麼synchronized這個關鍵字就十分重要的啦,多線程可以完成數據的共享,那麼當然要同步啦,就是說當我要訪問一個數據,再我沒改時,別人是不允許訪問的,要是別人也訪問了這個數據,在別人訪問後我又把數據改了,那麼別人在寫會是就會發生錯誤,即讀髒了數據。下面說一個比較容易理解的例子吧!
經典的銀行取款問題,有一個銀行賬戶,還有餘額2000元,現在A通過銀行卡從中取出800,而同時另外一個人B通過存摺也從這個賬戶中取了800。在A取錢的時候還沒等把餘額寫回,則另一個人B此時又取走了800,然後兩人同時將餘額寫回,那麼取了兩次,結果餘額還是1200。當然了這個問題在現實中很不現實,根本是不可能的事,所以要做到這點就要弄明白同步這個問題,當A取錢的時候,拒絕B來取錢,只有在A取完錢把餘額寫回了纔可以允許B進行取錢。
下面看一下兩人同時取錢的情況:
package day16; public class TestAccount { public static void main(String[] args) { Account account = new Account(); R r1 = new R(account); R r2 = new R(account); Thread th1 = new Thread(r1,"me"); Thread th2 = new Thread(r2,"her"); th1.start(); th2.start(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("餘額爲:"+account.balance); } } class Account{ public /*synchronized*/int balance = 2000; public void withDraw(){ int temp = balance; temp = temp - 800; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } balance = temp; System.out.println(Thread.currentThread().getName()+" "+this.balance); } } class R implements Runnable{ private Account account; public R(Account account){ this.account = account; } @Override public void run() { account.withDraw(); } } 運行結果: her 1200 me 1200 餘額爲:1200
通過對象鎖,我們可以避免多個線程在訪問同一個數據時不同步問題,但這種方式也很容易引起“死鎖”,所謂死鎖,就是指兩個線程,都相互等待對方釋放lock,如經典哲學家進餐的問題,這種情形是不可預測或避開的,我們應採取措施避免死鎖的出現。出現死鎖的程序將一直等待,所以要避免這種情況的出現,要避免死鎖,應該確保在獲取多個鎖時,在所有的線程中都以相同的順序獲取鎖。
下面再看一個解決同步問題的,即壓棧與彈棧的操作,在同一個棧時壓棧的時候不允許彈棧,反之亦然,記得數據結構中介紹的,彈棧時,指針先減,然後將內容彈出;入棧則相反,先將內容壓入,然後指針再加,因爲指針指的是棧top的再上一個位置,說道棧,在這裏再說一下java中棧和堆與引用和對象之間的關係吧:
如有一個Person類有name和age屬性
一下代碼:
Person p1 = new Person(“張三”,20);
Person p2 = p1;
則可以表示爲以下所示:
即p1表示引用,而new出來的則是對象,對象存在堆中,而引用則存在棧中,可以將引用看成地址,即p1和p2兩個引用擁有相同的地址所以指向了相同的區域,另外引用的地址相同,那麼說明他們指向的內容肯定相同,即equals相同,hascode也相同,但hascode相同時,不能說明地址也相同,也不能說明equals相同,hascode一般在hashSet和hashTable可以用到,在別處基本上不用。
下面再看一個解決同步問題的例子吧,再熟悉熟悉~
先定義了一個接口 package my03; public interface StackInterface { public void push(int n); public int[] pop(); } Push和pop方法 package my03; public class safeStack implements StackInterface{ private int top = 0; private int[] values = new int[10]; public void push(int n){ synchronized (this) { values[top]=n; System.out.println("壓入數字:"+n); top++; System.out.println("壓棧操作完成"); } } public int[] pop(){ synchronized (this) { System.out.print("彈棧"); top--; System.out.println(values[top]); int[] test = {values[top],top}; return test; } } } Pop線程 package my03; public class PopThread implements Runnable{ private StackInterface s; public PopThread(StackInterface s){ this.s = s; } public void run(){ System.out.println("---"+s.pop()[1]+"---"); } } Push線程 package my03; public class PushThread implements Runnable { public PushThread(){ } private StackInterface s; public PushThread(StackInterface s){ this.s = s; } public void run(){ for(int i = 0; i < 10; i++){ System.out.print("."); } int k = 23; s.push(k); } } 主方法: package my03; public class TestsafeStack { public static void main(String[] args) { safeStack s = new safeStack(); s.push(1); s.push(2); PushThread r1 = new PushThread(s); PopThread r2 = new PopThread(s); Thread th1 = new Thread(r1); Thread th2 = new Thread(r2); th1.start(); th2.start() } } 輸出如下: 壓入數字:1 壓棧操作完成 壓入數字:2 壓棧操作完成 .........彈棧.2 壓入數字:23 壓棧操作完成 ---1--- 由上可見,壓棧和彈棧操作內部是不允許中斷的~