1. 程式人生 > >Java中的鎖機制學習筆記

Java中的鎖機制學習筆記

今天回顧了下java中的鎖機制,和object類中中的wait(),notify(),notifyAll();每次敲程式碼的時候老看見這幾個方法,總讓人將它們和Android中的notifyDataSetChanged()的方法想到一塊去,其實這些東西在Java基礎的時候就已經學過了,可能是學的不紮實所有總有一種似成相識的感覺,來來回回,有一種身體被掏空了的感覺!

廢話不多說,看看這幾個方法的使用場景,在我們的程式碼難免遇到使用多個執行緒操作一個物件的情景,這樣,我們如果不對這個物件進行一定的保護(加鎖),那麼這個物件對於我們來說就是執行緒不安全的,下面就用一個簡單的賣票的小例子來演示一下:

package cn.dave.test;

public class TicketDemo {

    private int ticketNum=150;

    public static void main(String[] args) {
        TicketDemo ticketDemo=new TicketDemo();
        sellThread sThread0=ticketDemo.new sellThread();
        sellThread sThread1=ticketDemo.new sellThread();
        sellThread sThread2=ticketDemo.new
sellThread(); sellThread sThread3=ticketDemo.new sellThread(); sThread0.start(); sThread1.start(); sThread2.start(); sThread3.start(); } class sellThread extends Thread{ public void run() { while(ticketNum>0){ ticketNum--; System.out.println("票已賣出,現在還剩:"
+ticketNum); } } } }

程式碼非常的簡單,我們定義了150張票,並開啟了四個執行緒去賣,直到票的數量為0,看起來好像沒什麼問題,我們執行開一下Log日誌的輸出:

票已賣出,現在還剩:148
票已賣出,現在還剩:146
票已賣出,現在還剩:147
票已賣出,現在還剩:148
票已賣出,現在還剩:143
票已賣出,現在還剩:144
....

NO,程式剛開始執行就出問題了,148的時候賣了兩次,這就出現了執行緒安全的問題了,那我們可以怎麼解決這個問題了,這是我們就用到了Java中的synchronized機制了,我們先來看看加了鎖後的效果:

        synchronized (lock) {
                while(ticketNum>0){
                    ticketNum--;
                    System.out.println("票已賣出,現在還剩:"+ticketNum);
                }
            }

程式碼非常的簡單,我們加了一個同步程式碼塊,這樣讓我們每次只能同時只有一個執行緒可以訪問我們公共的物件對其進行操作,解決了執行緒的安全問題,那麼這個lock是什麼了?

private Object lock=new Object();//物件鎖

它是一個我們定義的類成員變數,作為我們的物件鎖,我們再看看這樣的執行結果:

票已賣出,現在還剩:149
票已賣出,現在還剩:148
票已賣出,現在還剩:147
票已賣出,現在還剩:146
票已賣出,現在還剩:145
票已賣出,現在還剩:144
...

這把就非常的漂亮了!那麼這個鎖我們以後都必須這樣寫嗎?當然不是了,Java中為我們提供瞭如下幾種方法:

  1. 在需要同步的方法的方法簽名中加入synchronized關鍵字。

  2. 使用synchronized塊對需要進行同步的程式碼段進行同步。

  3. 使用JDK 5中提供的java.util.concurrent.lock包中的Lock物件。

注意:在使用同步方法鎖的時候我們需要知道,當前方法持有的鎖是當前的物件,如果這個方法是我們類的成員方法,那麼這個方法的鎖物件就是一記憶體this,如果在多執行緒中使用方法鎖的話,就像上面賣票例子那樣,我們持有的鎖物件就不是類物件了,而是我們的多執行緒物件,這樣就造成了鎖的不唯一,沒法到達執行緒安全的目的了。

好了,上面就是我對鎖的見解,有不對的地方歡迎指正!

說了這麼半天,好像沒開篇說的wait()等這幾個方法沒有半毛錢的關係啊,不急,下面我就結合我自己的理解,來談談我對那幾個方法的理解。

我將Object類中的這幾個方法進行了一個比喻,輔助記憶,當一個物件呼叫這幾個方法的時候,就像一個動物園的飼養員要進行投放食物了,這是動物都處於wait()的狀態,當飼養員給一群沒有嚴格等級分明的動物投放食物的時候,就像呼叫了notifyAll()方法,一下正在wait()的動物就會一下全上來搶食,就看誰手快了。而如果是面對一群有等級分明的動物時,就像呼叫notify()方法,此時就只有一個老大先出來吃,等他完事了,在呼叫notify()通知下一個wait()的來吃,這樣依次執行,當然這只是舉個例子,Java中還是看臉來搶的···

說了這麼些,我們就來看個實際的例子:

package cn.dave.test;

public class TestNotify {

    private String[] flag = new String[] { "stop" };
    private Object lock=new Object(); 

    /**
     * @param args
     */
    public static void main(String[] args) {
        TestNotify test=new TestNotify();
        WaitThread waitThread=test.new WaitThread("wait");
        WaitedThread waitedThread1=test.new WaitedThread("waited1");
        WaitedThread waitedThread2=test.new WaitedThread("waited2");
        WaitedThread waitedThread3=test.new WaitedThread("waited3");
        waitThread.start();
        waitedThread1.start();
        waitedThread2.start();
        waitedThread3.start();
    }

    class WaitThread extends Thread {

        public WaitThread(String flag) {
            super(flag);
        }

        public void run() {
            try {

                Thread.sleep(2000);
                synchronized (lock) {
                    flag[0] = "begin";
                    lock.notify();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class WaitedThread extends Thread {

        public WaitedThread(String flag) {
            super(flag);
        }

        public void run() {
            synchronized (lock) {
                try {
                    while (flag[0].equals("stop")) {
                        System.out.println(getName() + "begin wait!");
                        long wait = System.currentTimeMillis();
                        lock.wait();
                        wait = System.currentTimeMillis() - wait;
                        System.out.println("wait time :" + wait);
                        Thread.sleep(2000);
                        lock.notify();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(getName() + " end waiting!");
        }
    }

}

在例子中我們建立了一個用於喚醒其他執行緒的執行緒waitThread,和三個等待的執行緒waitedThread1,waitedThread2,waitedThread3,在程式碼中大家也發現了我們增加了兩處同步程式碼塊,那麼為什麼了?因為不加的話會拋異常啊,哈哈!!!

waited2begin wait!Exception in thread "waited2" Exception in thread "waited3" Exception in thread "waited1" 
waited1begin wait!
waited3begin wait!
java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Unknown Source)
    at cn.dave.test.TestNotify$WaitedThread.run(TestNotify.java:52)
java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Unknown Source)
    at cn.dave.test.TestNotify$WaitedThread.run(TestNotify.java:52)
java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Unknown Source)
    at cn.dave.test.TestNotify$WaitedThread.run(TestNotify.java:52)
Exception in thread "wait" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at cn.dave.test.TestNotify$WaitThread.run(TestNotify.java:34)

出現這個的原因是因為:

  • 任何一個時刻,物件的控制權(monitor)只能被一個執行緒擁有。
  • 無論是執行物件的wait、notify還是notifyAll方法,必須保證當前執行的執行緒取得了該物件的控制權(monitor)
  • 如果在沒有控制權的執行緒裡執行物件的以上三種方法,就會報java.lang.IllegalMonitorStateException異常

那麼我們執行的結果如下:

waited2begin wait!
waited3begin wait!
waited1begin wait!
wait time :2000
waited2 end waiting!
wait time :4000
waited3 end waiting!
wait time :6000
waited1 end waiting!

貌似看起來沒有問題,但是我們看程式碼,發現有一句程式碼沒有處在同步程式碼塊中,這就會導致這句輸出語句的順序出現問題。

我們將程式碼稍微做點修改:

    flag[0] = "begin";
    lock.notifyAll();

    //去掉wait執行緒中的
    Thread.sleep(2000);
    lock.notify();

執行結果:

waited1begin wait!
waited2begin wait!
waited3begin wait!
wait time :2000
waited3 end waiting!
wait time :2000
waited2 end waiting!
wait time :2000
waited1 end waiting!

可以看到這次就是大家一起上了。

好了,今天就記到這吧!