Java多線程同步鎖的理解


java主要通過synchronized的關鍵字來實現的。讓我們從一個買票程序說起吧。

package com.day04;

/**
 * 
 * @author Administrator 問題描述:使用多線程的方式來模擬多個窗口買票
 *
 */
public class SaleWindow implements Runnable {

    // 初始化票數10
    private int ticket = 10;

    @Override
    public void run() {
        // 獲取線程的名稱,比如Thread-0,並將它截掉Thread-取0這個數字標識,為了構造下面賣票窗口名稱
        int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
        String saleWindowName = "銷售窗口" + threadNum;
        // 開始買票
        while (true) {
            if (ticket > 0) {
                // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時)
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !");
            } else {
                break;
            }
        }
    }

    public static void main(String[] args) {
        // 創建了銷售窗口對象
        SaleWindow sw = new SaleWindow();
        // 啟動線程,讓第一個窗口開始買票
        new Thread(sw).start();
        // 啟動線程,讓第二個窗口開始買票
        new Thread(sw).start();
        // 啟動線程,讓第三個窗口開始買票
        new Thread(sw).start();

    }

}

運行結果如下所示:

銷售窗口2 賣 出 了 10 號 票 !
銷售窗口1 賣 出 了 8 號 票 !
銷售窗口0 賣 出 了 9 號 票 !
銷售窗口2 賣 出 了 7 號 票 !
銷售窗口1 賣 出 了 6 號 票 !
銷售窗口0 賣 出 了 5 號 票 !
銷售窗口2 賣 出 了 4 號 票 !
銷售窗口1 賣 出 了 3 號 票 !
銷售窗口0 賣 出 了 2 號 票 !
銷售窗口2 賣 出 了 1 號 票 !
銷售窗口1 賣 出 了 0 號 票 !《-----
銷售窗口0 賣 出 了 -1 號 票 !《------
可以看到我們的程序出來了問題,上面打紅色箭頭所示,竟然賣出了0號票和-1號票了。

讓我們畫個圖來分析一下如下所示:

 

 通過以上分析,不難得出,造成問題原因,是因為同步操作問題。

 那我們如何確定哪些是同步操作(或者有同步問題)?

1.明確哪些代碼是多線程成運行的代碼(run方法中的代碼)

2.明確那些是共享數據(ticket票數)

3.明確多線程運行代碼中那些語句是操作共享數據(System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !");)

接下來我們就可以通過Java給我們提供的synchroized關鍵字使用同步鎖來解決以上的問題

 

package com.day04;

/**
 * @author Administrator 問題描述:使用多線程的方式來模擬多個窗口買票
 */
public class SaleWindow implements Runnable {

    // 初始化票數10
    private int ticket = 10;
    //線程的鎖
    private Object lock;

    @Override
    public void run() {
        // 獲取線程的名稱,比如Thread-0,並將它截掉Thread-取0這個數字標識,為了構造下面賣票窗口名稱
        int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
        String saleWindowName = "銷售窗口" + threadNum;
        // 開始買票
        while (true) {
            //加上synchronized,並加入對象鎖,new一個任意對象即可,我們這里使用Object來解決同步問題,注意這里必須是公用同一個鎖lock
            synchronized (lock) {

                if (ticket > 0) {
                    // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時)
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !");
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        // 創建了銷售窗口對象
        SaleWindow sw = new SaleWindow();
        // 啟動線程,讓第一個窗口開始買票
        new Thread(sw).start();
        // 啟動線程,讓第二個窗口開始買票
        new Thread(sw).start();
        // 啟動線程,讓第三個窗口開始買票
        new Thread(sw).start();

    }

}

 

運行結果如下所示:

銷售窗口1 賣 出 了 10 號 票 !

銷售窗口2 賣 出 了 9 號 票 !

銷售窗口0 賣 出 了 8 號 票 !

銷售窗口0 賣 出 了 7 號 票 !

銷售窗口2 賣 出 了 6 號 票 !

銷售窗口1 賣 出 了 5 號 票 !

銷售窗口2 賣 出 了 4 號 票 !

銷售窗口0 賣 出 了 3 號 票 !

銷售窗口0 賣 出 了 2 號 票 !

 

銷售窗口2 賣 出 了 1 號 票 !

這樣就有效的解決了同步的問題。

同樣我們也可將上面的操作共享數據的同步操作抽取出來,單獨封裝成一個同步方法,只需要在方法上面的返回值前面加上synchronized關鍵字即可,這樣可以更方面理解和閱讀,優化后代碼如下。

package com.day04;

/**
 * 
 * @author Administrator 問題描述:使用多線程的方式來模擬多個窗口買票
 *
 */
public class SaleWindow implements Runnable {

    // 初始化票數10
    private int ticket = 10;

    @Override
    public void run() {

        // 開始買票
        while (true) {
            // 當沒有票了結束
            if (!saleSuccess()) {
                break;
            }
        }
    }

    public synchronized boolean saleSuccess() {
        // 獲取線程的名稱,比如Thread-0,並將它截掉Thread-取0這個數字標識,為了構造下面賣票窗口名稱
        int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
        String saleWindowName = "銷售窗口" + threadNum;
        if (ticket > 0) {
            // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時)
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !");
            return true;
        } else {
            return false;
        }
    }

    public static void main(String[] args) {
        // 創建了銷售窗口對象
        SaleWindow sw = new SaleWindow();
        // 啟動線程,讓第一個窗口開始買票
        new Thread(sw).start();
        // 啟動線程,讓第二個窗口開始買票
        new Thread(sw).start();
        // 啟動線程,讓第三個窗口開始買票
        new Thread(sw).start();

    }

}

 

銷售窗口1 賣 出 了 10 號 票 !銷售窗口2 賣 出 了 9 號 票 !銷售窗口0 賣 出 了 8 號 票 !銷售窗口0 賣 出 了 7 號 票 !銷售窗口2 賣 出 了 6 號 票 !銷售窗口1 賣 出 了 5 號 票 !銷售窗口2 賣 出 了 4 號 票 !銷售窗口0 賣 出 了 3 號 票 !銷售窗口0 賣 出 了 2 號 票 !銷售窗口2 賣 出 了 1 號 票 !

 

現在又有一個問題出現了,public synchronized boolean saleSuccess()該同步函數用的是哪一個鎖?

我們猜想可能用的是this這個對象鎖,如果我們讓線程一個執行帶有sychronized的同步方法,一個執行帶有this對象的sychronized同步代碼塊的方法,如果能夠得到正確的結果,不出現同步問題,即論證正確,反之,如果還是出現同步問題即用的不是this這個對象鎖。代碼如下:

package com.day04;

/**
 * 
 * @author Administrator 問題描述:使用多線程的方式來模擬多個窗口買票
 *
 */
public class SaleWindow implements Runnable {

    // 初始化票數10
    private int ticket = 10;

    @Override
    public void run() {
        // 獲取當前線程的序號從0開始
        int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
        // 偶數線程執行該方法
        if ((threadNum + 1) % 2 == 0) {
            while (true) {
                synchronized (this) {
                    String saleWindowName = "奇數銷售窗口" + threadNum;
                    if (ticket > 0) {
                        // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時)
                        try {
                            Thread.sleep(1000L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !");
                    } else {
                        break;
                    }
                }
            }
        } else {
            // 奇數線程執行該方法
            // 開始買票
            while (true) {
                // 當沒有票了結束
                if (!saleSuccess()) {
                    break;
                }
            }
        }

    }

    public synchronized boolean saleSuccess() {
        // 獲取線程的名稱,比如Thread-0,並將它截掉Thread-取0這個數字標識,為了構造下面賣票窗口名稱
        int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
        String saleWindowName = "偶數銷售窗口" + threadNum;
        if (ticket > 0) {
            // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時)
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !");
            return true;
        } else {
            return false;
        }
    }

    public static void main(String[] args) {
        // 創建了銷售窗口對象
        SaleWindow sw = new SaleWindow();
        // 啟動線程,讓第一個窗口開始買票
        new Thread(sw).start();
        // 啟動線程,讓第二個窗口開始買票
        new Thread(sw).start();
        // 啟動線程,讓第三個窗口開始買票
        new Thread(sw).start();

    }

}

運行結果如下:

偶數銷售窗口0 賣 出 了 10 號 票 !

偶數銷售窗口0 賣 出 了 9 號 票 !

偶數銷售窗口2 賣 出 了 8 號 票 !

奇數銷售窗口1 賣 出 了 7 號 票 !

偶數銷售窗口2 賣 出 了 6 號 票 !

偶數銷售窗口2 賣 出 了 5 號 票 !

偶數銷售窗口2 賣 出 了 4 號 票 !

偶數銷售窗口2 賣 出 了 3 號 票 !

偶數銷售窗口2 賣 出 了 2 號 票 !

 

偶數銷售窗口0 賣 出 了 1 號 票 !

由上面的接口即可論證同步方法使用對的對象鎖是this。

同樣的加入我們的將我們的共享數據ticket改成靜態的,並將同步方法也改成靜態,它用的是那個對象鎖?

我們猜想是本類的class對象這個鎖即(SaleWindow.class)這個對象鎖。同理如果我們讓線程一個執行帶有sychronized的靜態同步方法,一個執行帶有本類的class對象這個鎖即(SaleWindow.class)的sychronized同步代碼塊的方法,如果能夠得到正確的結果,不出現同步問題,即論證正確。反之,如果還是出現同步問題,即說明靜態同步方法使用的鎖不是本類的class對象這個鎖即(SaleWindow.class)這個對象鎖。代碼如下:

package com.day04;

/**
 * 
 * @author Administrator 問題描述:使用多線程的方式來模擬多個窗口買票
 *
 */
public class SaleWindow implements Runnable {

    // 初始化票數10
    private static int ticket = 10;

    @Override
    public void run() {
        // 獲取當前線程的序號從0開始
        int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
        // 偶數線程執行該方法
        if ((threadNum + 1) % 2 == 0) {
            while (true) {
                synchronized (SaleWindow.class) {
                    String saleWindowName = "奇數銷售窗口" + threadNum;
                    if (ticket > 0) {
                        // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時)
                        try {
                            Thread.sleep(1000L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !");
                    } else {
                        break;
                    }
                }
            }
        } else {
            // 奇數線程執行該方法
            // 開始買票
            while (true) {
                // 當沒有票了結束
                if (!saleSuccess()) {
                    break;
                }
            }
        }

    }

    public synchronized static boolean saleSuccess() {
        // 獲取線程的名稱,比如Thread-0,並將它截掉Thread-取0這個數字標識,為了構造下面賣票窗口名稱
        int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));
        String saleWindowName = "偶數銷售窗口" + threadNum;
        if (ticket > 0) {
            // 這里為了演示出線程不同步的問題,讓線程睡眠一段時間,延時)
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(saleWindowName + " 賣 出 了 " + ticket-- + " 號 票 !");
            return true;
        } else {
            return false;
        }
    }

    public static void main(String[] args) {
        // 創建了銷售窗口對象
        SaleWindow sw = new SaleWindow();
        // 啟動線程,讓第一個窗口開始買票
        new Thread(sw).start();
        // 啟動線程,讓第二個窗口開始買票
        new Thread(sw).start();
        // 啟動線程,讓第三個窗口開始買票
        new Thread(sw).start();

    }

}

運行結果如下:

偶數銷售窗口0 賣 出 了 10 號 票 !

偶數銷售窗口0 賣 出 了 9 號 票 !

偶數銷售窗口2 賣 出 了 8 號 票 !

奇數銷售窗口1 賣 出 了 7 號 票 !

偶數銷售窗口2 賣 出 了 6 號 票 !

偶數銷售窗口2 賣 出 了 5 號 票 !

偶數銷售窗口2 賣 出 了 4 號 票 !

偶數銷售窗口2 賣 出 了 3 號 票 !

偶數銷售窗口2 賣 出 了 2 號 票 !

 

偶數銷售窗口0 賣 出 了 1 號 票 !

由上面的接口即可論證同步方法使用對的對象鎖是本類的class對象這個鎖即(SaleWindow.class)這個對象鎖。

public synchronized boolean saleSuccess()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM