電影院售票系統的模擬及利用synchronized方法實現同步

NO IMAGE
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

前面我們學習了程序的兩種實現方式,第一種:繼承Thread類,第二種:實現Runnable介面,首先我們來對比一下兩種實現方式:

第一種方式:

    1)自定義MyThread 類繼承自Thread

    2)重寫Thread類中的run()方法

    3)建立MyThread類物件,分別去啟動執行緒

注意:啟動執行緒的時候不呼叫run()方法,因為run()不能作為啟動執行緒的方法,該方法的呼叫相當於呼叫一個普通方法,並不會出現執行緒執行的一種隨機性!所以啟動執行緒用的是start()方法,start方法的執行是通過JVM呼叫run方法,但一個程序不要連續啟動,否則會出現非法執行緒狀態異常!

第二種方式:

    1)自定義MyRunnable類實現Runnable介面

    2)實現介面的run()方法

    3)建立MyRunnable類物件,建立Thread類物件,將MyRunnable類物件作為引數進行傳遞,分別啟動執行緒。

那麼你可能會問:既然已經有了第一種實現方式,為什麼還會有第二種呢?因為第二種方式實際上是優於第一種的

1)避免了Java單繼承的一種侷限性

2)更符合Java物件導向的一種設計原則:面向介面程式設計。將程式碼的實現和資源物件(MyRunnable)有效地分離開來(資料分離原則)

接下來說一下執行緒的生命週期:執行緒從開始建立的時候,一直到執行緒的執行,最後到執行緒的終止!

    新建執行緒:此時執行緒沒有執行執行資格,沒有執行權。

    執行緒就緒:執行緒有執行資格了,但是沒有執行權,一旦該執行緒搶到了CPU的執行權,執行緒就開始執行了。

    在執行執行緒之前,執行緒還可能會阻塞,如sleep()和wait()方法,此時執行緒處於阻塞狀態,睡眠時間到了或執行notify()方法來喚醒程序,執行緒物件.start();

    執行緒執行:執行緒有執行資格,並且有執行權,此時該執行緒如果被別的執行緒搶佔到了CPU的執行權,執行緒就處於就緒的狀態。

    執行緒死亡:執行緒執行完畢,會被垃圾回收執行緒中的垃圾回收器及時從記憶體中釋放掉!

這次我們就通過這兩種方式來實現一個電影院售票的小案例:

需求:某電影院出售某些電影的票(復聯3,紅高粱….),有三個視窗同時進行售票(100張票),請您設計一個程式,模擬電影院售票
  兩種方式:
  繼承

  介面

方式一:

//SellTicket執行緒
public class SellTicket extends Thread {
//為了不讓外界更改這個類中的資料,用private修飾
//要讓每一個執行緒都使用同一個資料,應該用static修飾
private static int tickets = 100 ;
@Override
public void run() {
//為了模擬電影賣票(模擬一直有票)
//死迴圈
//st1,st2,st3都有執行這個裡面的方法
while(true) {
if(tickets>0) {
System.out.println(getName() "正在出售第" (tickets--) "張票");
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//建立三個子執行緒,分別程式碼三個視窗
SellTicket st1 = new SellTicket() ;
SellTicket st2 = new SellTicket() ;
SellTicket st3 = new SellTicket() ;
//設定執行緒名稱
st1.setName("視窗1");
st2.setName("視窗2");
st3.setName("視窗3");
//啟動執行緒
st1.start();
st2.start();
st3.start();
}
}

注意:這裡的票數必須被static修飾,否則程序1、程序2和程序3會各自列印100張票,被static修飾之後可以實現資料的共享,3個程序共同列印100張票。

方式二:

public class SellTicket implements Runnable {
//定義100張票
private int tickets = 100 ;
@Override
public void run() {
while(true) {
if(tickets>0) {
System.out.println(Thread.currentThread().getName()
"正在出售第" (tickets--) "張票");
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//建立資源類物件(共享資源類/目標物件)
SellTicket st = new SellTicket() ;
//建立執行緒類物件
Thread t1 = new Thread(st, "視窗1") ;
Thread t2 = new Thread(st ,"視窗2") ;
Thread t3 = new Thread(st, "視窗3") ;
//啟動執行緒
t1.start();
t2.start();
t3.start();
}
}

這裡的Runnable介面實現的方法中之所以票數不用被static修飾,是因為設計該介面的目的是為希望在活動時執行程式碼的物件提供一個公共協議,也就是說這裡的SellTicket作為Runnable的子實現類,其中的資料就已經是共享的了,所以不用再被static修飾。

為了模擬更真實的場景,加入延遲操作(讓執行緒睡100毫秒)

public class SellTicket implements Runnable {
//定義100張票
private int tickets = 100 ;
@Override
public void run() {
while(true) {
try {
//t1睡 t2睡
Thread.sleep(100); //t2睡
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tickets>0) {
//t1,t2,t3 三個執行緒執行run裡面程式碼
//為了模擬更真實的場景(網路售票有延遲的),稍作休息	
System.out.println(Thread.currentThread().getName()
"正在出售第" (tickets--) "張票");//0
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//建立資源類物件(共享資源類/目標物件)
SellTicket st = new SellTicket() ;
//建立執行緒類物件
Thread t1 = new Thread(st, "視窗1") ;
Thread t2 = new Thread(st ,"視窗2") ;
Thread t3 = new Thread(st, "視窗3") ;
//啟動執行緒
t1.start();
t2.start();
t3.start();
}
}

程式的設計是好的,但是結果有一些問題:
1)同一張票被賣了多次

 CPU的執行有一個特點(具有原子性操作:最簡單最基本的操作)

t1執行緒進來,睡完了,100張票
原子性操作:記錄以前的值
接著tickets– :票變成99張票
在馬上輸出99張票之前,t2/t3進來,直接輸出記錄的以前那個tickets的值
出現:
視窗1正在出售第100張票
視窗3正在出售第99張票
視窗2正在出售第99張票

2)出現了0或者負票

    (延遲操作 執行緒的執行隨機性)

                理想狀態:
t1正在出售第3張票

t3正在出售第2張票
t2正在出售第1張票
  …
負票
t1出售第0張票 (延遲操作 執行緒的執行隨機性)
t3正在出售-1張票

通過剛才的這個程式,有安全問題(同票還有負票問題)

如何解決多執行緒的安全問題?
校驗一個多執行緒程式是否有安全問題的隱患的前提條件:
1)當前程式是否是多執行緒環境
2)是否有共享資料
3)是否有多條語句對共享資料進行操作

看當前案例是否有多執行緒的安全問題:
1)是否是多執行緒環境 是
2)是否有共享資料 是
3)是否有多條語句對共享資料進行操作  是

現在就需要解決安全問題:
1)多執行緒環境 不能解決
2)對共享資料進行優化 不能解決
3)解決將多條語句對共享資料這一環進行解決

解決方案:就是將多條語句對共享資料操作的程式碼,用一個程式碼包起來—->程式碼—>同步程式碼塊

格式:
synchronized(鎖物件){
針對多條語句對共享資料操作程式碼;
}

鎖物件:肯定一個物件,隨便建立一個物件(匿名物件) 
給剛才的這個程式加入了同步程式碼塊,但是鎖物件使用的匿名物件(每一個執行緒進來都有自己的鎖),還是沒有解決!

鎖物件:每一個執行緒最終使用的鎖物件,只能是同一把鎖

public class SellTicket implements Runnable {
//定義100張票
private int tickets = 100 ;
private Object obj = new Object() ;
@Override
public void run() {
while(true) {
//new Object():鎖物件 (門和關),使用匿名物件的方式,有問題!
//t1,t2,t3
/*synchronized(new Object()) {//t1
if(tickets>0) {
try {
//睡眠:延遲
Thread.sleep(100); 
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
"正在出售第" (tickets--) "張票");//0,-1
}
}*/
synchronized(obj) {//t1進來,門一關,t2,t3進不來了
if(tickets>0) {
try {
//睡眠:延遲
Thread.sleep(100); 
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
"正在出售第" (tickets--) "張票");//0,-1
}
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//建立資源類物件(共享資源類/目標物件)
SellTicket st = new SellTicket() ;
//建立執行緒類物件
Thread t1 = new Thread(st, "視窗1") ;
Thread t2 = new Thread(st ,"視窗2") ;
Thread t3 = new Thread(st, "視窗3") ;
//啟動執行緒
t1.start();
t2.start();
t3.start();
}
}

舉例:
火車上上廁所(鎖物件:將它看成是門的開和關)

synchronized(鎖物件){
多條語句對共享資料操作的程式碼;
}
注意:
鎖物件:一定要同一個鎖(每個執行緒只能使用同一把鎖)

鎖物件:任何的Java類(引用型別)

public class SellTicket implements Runnable {
//定義100張票
private int tickets = 100 ;
private Object obj = new Object() ;
private Demo d = new Demo() ;
@Override
public void run() {
while(true) {
//t1,t2,t3
synchronized(d) { //門的開和關
//t1進來,門會關掉
//t2進來,門關掉
//t3進來,門關掉
if(tickets>0) {
try {
//0.1
Thread.sleep(100); 
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
"正在出售第" (tickets--) "張票");
//視窗1正在出售第100張票
//視窗2正在出售第99張票
//視窗3正在出售98張票
//....
//雖然加入延遲操作,就是synchronized,不會存在0或者負票了
}
}
}
}
}
class Demo{
}

下來我們通過一個例子來學習一下同步方法:

public class SellTicket implements Runnable {
//定義100張票
private  static int tickets = 100 ;
private Object obj = new Object() ;
//定義一個變數
private int x = 0 ;
@Override
public void run() {
while(true) {
if(x %2==0) {
synchronized(SellTicket.class) { //門的開和關  :靜態方法的鎖物件:類名.class
if(tickets>0) {
try {
//0.1
Thread.sleep(100); 
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
"正在出售第" (tickets--) "張票");
}
}
}else {
/*synchronized(d) { //門的開和關
if(tickets>0) {
try {
Thread.sleep(100); 
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
"正在出售第" (tickets--) "張票");
}
}*/
//將其寫成方法
sellTicket() ;
}
x    ;
}
}
//如果一個方法一進來就是同步程式碼塊,那麼可不可以將同步放到方法來進行宣告呢? 可以
/*private void sellTicket() {
synchronized(d) { 
if(tickets>0) {
try {
Thread.sleep(100); 
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
"正在出售第" (tickets--) "張票");
}
}
}*/
//非靜態的方法:同步方法(需要底層原始碼,一些方法會宣告synchronized)的鎖物件:this
/*private synchronized  void sellTicket() {
if(tickets>0) {
try {
Thread.sleep(100); 
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
"正在出售第" (tickets--) "張票");
}
}*/
//靜態的同步方法:和反射有關 (靜態同步方法的鎖物件:類名.class)
private synchronized static  void sellTicket() {
if(tickets>0) {
try {
Thread.sleep(100); 
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
"正在出售第" (tickets--) "張票");
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//建立資源類物件(共享資源類/目標物件)
SellTicket st = new SellTicket() ;
//建立執行緒類物件
Thread t1 = new Thread(st, "視窗1") ;
Thread t2 = new Thread(st ,"視窗2") ;
Thread t3 = new Thread(st, "視窗3") ;
//啟動執行緒
t1.start();
t2.start();
t3.start();
}
}

相關文章

程式語言 最新文章