Java基礎多執行緒之執行緒安全-同步鎖三種形式
阿新 • • 發佈:2018-11-26
首先,我們通過一個案例,演示執行緒的安全問題:
電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “葫蘆娃大戰奧特曼”,本次電影的座位共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(); // 釋放鎖
}
}