1. 程式人生 > >Java基礎多執行緒之執行緒安全-同步鎖三種形式

Java基礎多執行緒之執行緒安全-同步鎖三種形式

首先,我們通過一個案例,演示執行緒的安全問題:
電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “葫蘆娃大戰奧特曼”,本次電影的座位共100個(本場電影只能賣100張票)。我們來模擬電影院的售票視窗,實現多個視窗同時賣 “終結者”這場電影票(多個視窗一起賣這100張票)需要視窗,採用執行緒物件來模擬;需要票,Runnable介面子類來模擬模擬票:

public class MyTicketWrong implements Runnable {

    private int tickets = 100; // 總共100張票

    @Override
    public void
run() { String name = Thread.currentThread().getName(); while (true) { if (tickets > 0) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name + "賣出了票:"
+ tickets--); } } } }

測試類;

public class Demo02TicketWrong {

    public static void main(String[] args) {
        Runnable task = new MyTicketWrong();

        new Thread(task, "A").start();
        new Thread(task, "B").start();
        new Thread(task, "C").start();
    }

}

此時會出現下面一種現象:
這裡寫圖片描述
出現了0甚至負數,出現了錯誤,這是由於執行緒不安全導致的,圖解如下:
這裡寫圖片描述
解決方案如圖下所示:
這裡寫圖片描述

採用同步鎖來保證執行緒安全:

(一)、同步程式碼塊
直接把最開始的程式碼修改一下

package day06.demo03;

/*
同步程式碼塊的格式:
synchronized (鎖物件) {
    // ...
}

小括號當中必須是一個物件引用型別,不能是基本型別。
含義:
只要程式碼執行到sync所在的一行,當前執行緒就會嘗試霸佔小括號當中的鎖。
如果霸佔成功,那麼立刻進入大括號執行內容;如果霸佔失敗,就會卡死在這一行,直到有人釋放鎖再重新搶。
直到離開了大括號的範圍,就算釋放了鎖。
 */
public class MyTicketSyncBlock implements Runnable {

    private int tickets = 100; // 總共100張票
    private final Object LOCK = new Object(); // 全域性唯一的鎖物件

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        while (true) {
            synchronized (LOCK) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(name + "賣出了票:" + tickets--);
                }
            } // sync
        }
    }
}

(二)同步方法

package day06.demo03;

/*
同步方法的格式:

修飾符 synchronized 返回值型別 方法名稱(引數列表) {
    方法體
}

對於同步方法來說,其實也有鎖物件:
1. 對於普通的成員方法來說:鎖物件其實是this當前物件。
2. 對於靜態的方法來說:
修飾符 static synchronized 返回值型別 方法名稱(引數列表) {...}
靜態同步方法的鎖物件其實是:該類的反射物件,也就是“類名稱.class”。(day14學習反射的時候瞭解。)
 */
public class MyTicketSyncMethod implements Runnable {

    private int tickets = 100; // 總共100張票

    @Override
    public void run() {
        while (true) {
            sell();
        }
    }

    private synchronized void sell() {
        if (tickets > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name = Thread.currentThread().getName();
            System.out.println(name + "賣出了票:" + tickets--);
        }
    }
}

(三)Lock

package day06.demo03;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
java.util.concurrent.locks.Lock介面:

public void lock():嘗試霸佔鎖,如果成功則正常完成;如果霸佔不成功,則一直卡住,直到成功為止。
public void unlock():釋放鎖。

使用步驟:
1. 首先建立一個鎖物件:Lock lock = new ReentrantLock();
2. 要想上鎖:呼叫lock()方法。相當於“synchronized (鎖) {”
3. 要是釋放鎖:呼叫unlock()方法。相當於“...}”
 */
public class MyTicketSyncLock implements Runnable {

    private int tickets = 100; // 總共100張票
    private final Lock LOCK = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            sell();
        }
    }

    private void sell() {
        LOCK.lock(); // 上鎖,嘗試霸佔鎖
        if (tickets > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name = Thread.currentThread().getName();
            System.out.println(name + "賣出了票:" + tickets--);
        }
        LOCK.unlock(); // 釋放鎖
    }
}