一、并發下售票存在的Bug
首先讓我們我們定義一個資源類Ticket我們通過多個執行緒來操作這一資源類,模擬賣票的例子:
//資源類
class Ticket{
//屬性總共還剩有多少張票
private int number=50;
//賣票的方法
public void sale(){
if (number>0){
//賣掉一張票
number--;
try {
//延遲,目的是為了更好看出在并發下程式運行的效果
TimeUnit.MILLISECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "賣出了" + (50-number) + "張票,剩余"+number+"張票");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
當我們在沒有任何加鎖的情況下進行多個執行緒并發操作:
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并發:多個執行緒操作同一資源類,把資源類丟入執行緒
Ticket ticket=new Ticket();
//@FunctionalInterface 函式式介面,jdk8 lambda運算式
new Thread(()->{ for (int i = 0; i < 10; i++) ticket.sale(); },"A").start();
new Thread(()->{ for (int i = 0; i < 10; i++) ticket.sale(); },"B").start();
new Thread(()->{ for (int i = 0; i < 30; i++) ticket.sale(); },"C").start();
}
}
這時候我們看一下運行結果:
B賣出了3張票,剩余47張票
A賣出了3張票,剩余47張票
C賣出了3張票,剩余47張票
C賣出了6張票,剩余44張票
B賣出了6張票,剩余44張票
A賣出了6張票,剩余44張票
A賣出了9張票,剩余41張票
C賣出了9張票,剩余41張票
B賣出了9張票,剩余41張票
C賣出了12張票,剩余38張票
A賣出了12張票,剩余38張票
B賣出了12張票,剩余38張票
B賣出了15張票,剩余35張票
A賣出了16張票,剩余34張票
C賣出了17張票,剩余33張票
A賣出了18張票,剩余32張票
B賣出了18張票,剩余32張票
C賣出了18張票,剩余32張票
C賣出了21張票,剩余29張票
A賣出了21張票,剩余29張票
B賣出了21張票,剩余29張票
B賣出了24張票,剩余26張票
C賣出了24張票,剩余26張票
A賣出了24張票,剩余26張票
A賣出了27張票,剩余23張票
B賣出了27張票,剩余23張票
C賣出了27張票,剩余23張票
A賣出了30張票,剩余20張票
B賣出了30張票,剩余20張票
C賣出了30張票,剩余20張票
C賣出了31張票,剩余19張票
C賣出了32張票,剩余18張票
C賣出了33張票,剩余17張票
C賣出了34張票,剩余16張票
C賣出了35張票,剩余15張票
C賣出了36張票,剩余14張票
C賣出了37張票,剩余13張票
C賣出了38張票,剩余12張票
C賣出了39張票,剩余11張票
C賣出了40張票,剩余10張票
C賣出了41張票,剩余9張票
C賣出了42張票,剩余8張票
C賣出了43張票,剩余7張票
C賣出了44張票,剩余6張票
C賣出了45張票,剩余5張票
C賣出了46張票,剩余4張票
C賣出了47張票,剩余3張票
C賣出了48張票,剩余2張票
C賣出了49張票,剩余1張票
C賣出了50張票,剩余0張票
明顯能看出運行結果不對勁兒,那為什么會出現這種情況呢???
很簡單:在沒有鎖的情況下多個執行緒沒有排隊,當訪問同一資源的時候多個執行緒看到的資源數量相同,當搶到資源的那幾個執行緒執行結束后,回頭看見的剩余資源又是一樣的,這就形成了上面圖中多個執行緒執行,列印結果出現一樣的情況,

二、synchronized鎖解決售票問題
synchronized鎖
package com.xiaochao;
import java.util.concurrent.TimeUnit;
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并發:多個執行緒操作同一資源類,把資源類丟入執行緒
Ticket ticket=new Ticket();
//@FunctionalInterface 函式式介面,jdk8 lambda運算式
new Thread(()->{ for (int i = 0; i < 10; i++) ticket.sale(); },"A").start();
new Thread(()->{ for (int i = 0; i < 10; i++) ticket.sale(); },"B").start();
new Thread(()->{ for (int i = 0; i < 30; i++) ticket.sale(); },"C").start();
}
}
//資源類 OOP
class Ticket{
//屬性、方法
private int number=50;
//賣票的方式
//synchronized 本質:佇列,排隊,鎖
public synchronized void sale(){
if (number>0){
number--;
try {
//延遲,目的是為了更好看出在并發下程式運行的效果
TimeUnit.MILLISECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "賣出了" + (50-number) + "張票,剩余"+number+"張票");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
在上面的代碼中我們能看到,我們將Ticket資源類的sale()方法添加了一個synchronized關鍵字,
這時候我們來看一下運行的結果是什么樣的:
A賣出了1張票,剩余49張票
A賣出了2張票,剩余48張票
C賣出了3張票,剩余47張票
C賣出了4張票,剩余46張票
C賣出了5張票,剩余45張票
C賣出了6張票,剩余44張票
C賣出了7張票,剩余43張票
C賣出了8張票,剩余42張票
C賣出了9張票,剩余41張票
C賣出了10張票,剩余40張票
C賣出了11張票,剩余39張票
C賣出了12張票,剩余38張票
C賣出了13張票,剩余37張票
C賣出了14張票,剩余36張票
C賣出了15張票,剩余35張票
B賣出了16張票,剩余34張票
B賣出了17張票,剩余33張票
B賣出了18張票,剩余32張票
B賣出了19張票,剩余31張票
B賣出了20張票,剩余30張票
B賣出了21張票,剩余29張票
B賣出了22張票,剩余28張票
B賣出了23張票,剩余27張票
B賣出了24張票,剩余26張票
B賣出了25張票,剩余25張票
C賣出了26張票,剩余24張票
C賣出了27張票,剩余23張票
C賣出了28張票,剩余22張票
C賣出了29張票,剩余21張票
C賣出了30張票,剩余20張票
C賣出了31張票,剩余19張票
C賣出了32張票,剩余18張票
C賣出了33張票,剩余17張票
C賣出了34張票,剩余16張票
C賣出了35張票,剩余15張票
C賣出了36張票,剩余14張票
C賣出了37張票,剩余13張票
A賣出了38張票,剩余12張票
A賣出了39張票,剩余11張票
A賣出了40張票,剩余10張票
A賣出了41張票,剩余9張票
A賣出了42張票,剩余8張票
A賣出了43張票,剩余7張票
A賣出了44張票,剩余6張票
A賣出了45張票,剩余5張票
C賣出了46張票,剩余4張票
C賣出了47張票,剩余3張票
C賣出了48張票,剩余2張票
C賣出了49張票,剩余1張票
C賣出了50張票,剩余0張票
下面我們將這個情節畫圖展示出來:

三、Lock鎖解決售票問題
Lock鎖
package com.xiaochao;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: demoCode
* @description:
* @author: 小超
* @create: 2020-12-29 15:15
**/
public class SaleTicketDemo02 {
public static void main(String[] args) {
//并發:多個執行緒操作同一資源類,把資源類丟入執行緒
Ticket2 ticket=new Ticket2();
//@FunctionalInterface 函式式介面,jdk8 lambda運算式
new Thread(()->{ for (int i = 0; i < 10; i++) ticket.sale(); },"A").start();
new Thread(()->{ for (int i = 0; i < 10; i++) ticket.sale(); },"B").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"C").start();
}
}
//資源類 OOP
//lock三部曲:1.new鎖2.加鎖,3.解鎖
class Ticket2{
//屬性、方法
private int number=50;
//new 鎖
Lock lock=new ReentrantLock();
public void sale(){
//加鎖
lock.lock();
if (number>0){
number--;
try {
//延遲
TimeUnit.MILLISECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "賣出了" + (50-number) + "張票,剩余"+number+"張票");
} catch (Exception e) {
e.printStackTrace();
}finally {
//解鎖,必須執行的步驟,不然會造成死鎖
lock.unlock();
}
}
}
}
四、synchronized和Lock的區別
synchronized與Lock的區別
- synchronized是關鍵字,Lock是一個介面
- synchronize無法判斷獲取鎖的狀態,Lock可以判斷是否獲取到了鎖
- synchronized會自動釋放鎖,Lock必須要手動釋放鎖,如果不釋放鎖就會造成死鎖
- synchronized 執行緒1(獲得鎖)、執行緒2(會等待);Lock鎖就不一定會等下去了,
- synchronized 可重入鎖,不可以中斷,非公平;Lock:可重入鎖,可以判斷,非公平(可以自己設定);
- synchrnoized 適合鎖少量的代碼同步問題,Lock鎖適合鎖大量的同步代碼塊!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/244231.html
標籤:其他
