1. 程式人生 > >11.多線程&&並發

11.多線程&&並發

但是 pack dea end ont 運行速度 觀察 點擊 進程和線程

11.1 操作系統中線程和進程的概念

一些常見的概念:

程序:指令和數據的byte序列,eg:qq.exe;a2.

進程:正在運行的程序(如QQ);a3.一個進程中可能有一到多個線程.

線程的概念:Thread 每個正在系統上運行的程序都是一個進程。每個進程包含一到多個線程。進程也可能是整個程序或者是部分程序的動態執行。線程是一組指令的集合,或者是程序的特殊段,它可以在程序裏獨立執行。也可以把它理解為代碼運行的上下文。所以線程基本上是輕量級的進程,它負責在單個程序裏執行多任務。通常由操作系統負責多個線程的調度和執行。 (為什麽不由進程來控制和調度線程呢,這是因為內核會把進程看做他運行的最小單位,當進程中(實際可能只是進程中某個線程)出現IO或者硬盤等一些耗時操作時,此時內核會把進程整個掛起來,此時進程中的其他線程也會被掛起來,想想一下一個文字處理器軟件,該軟件有自動保存功能,只因為實現自動保存的線程發起了IO調用,內核就把進程掛了起來了,此時主管視圖的線程也被掛了起來,此時我們會看到什麽情況呢,軟件打開著卻不能進行任何操作,過一會進程運行然後就可以操作,然後再自動保存,有被掛起來,循序的進行這樣的尷尬事.而如果由操作系統即內核負責的話,那麽他只會把負責自動保存的線程掛起來,不影響其他操作,只是讓自動保存的這個線程一會運行,一會阻塞,一會就緒,然後再運行,不會影響進程的運行!)

多線程的概念: 多線程是為了同步完成多項任務,不是為了提高運行效率,而是為了提高資源使用效率來提高系統的效率多線程是在同一時間需要完成多項任務的時候實現的。

多線程的優點:使用線程可以把占據長時間的程序中的任務放到後臺去處理

           用戶界面可以更加吸引人,這樣比如用戶點擊了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度  ·
程序的運行速度可能加快 ·在一些等待的任務實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較有用了。
在這種情況下我們可以釋放一些珍貴的資源如內存占用等等。

11.1.1 線程和進程的概念及關系

  現在的操作系統是多任務操作系統。多線程是實現多任務的一種方式。進程是指一個內存中運行的應用程序,每個進程都有自己獨立的一塊內存空間,一個進程中可以啟動多個線程。比如在Windows系統中,一個運行的exe就是一個進程。

  線程是指進程中的一個執行流程,一個進程中可以運行多個線程。比如java.exe進程中可以運行很多線程。線程總是屬於某個進程,進程中的多個線程共享進程的內存。“同時”執行是人的感覺,在線程之間實際上輪換執行

  內存執行進程和線程都是輪換執行,而不是把一個進程或線程執行完,才執行下一個,那樣會造成用戶的長時間等待;雖然在同一時刻只有一個進程在運行,但是很多程序在短時間內不斷地切換,在外界看來,似乎多個程序在同時執行.對於以秒為計算單位的人類來說,CPU給每個程序的運行時間只有幾十毫秒甚至納秒,進程的循環往復使我們看起來所有程序在同時運行.這樣兼顧了效率和公平.

  線程的運行和進程有異曲同工之妙,線程被運行完,線程生命周期消失,但有一類線程的生命周期卻是很長的,由服務器決定,那就是線程池,線程池中的線程運行完後還到線程池,用的時候再拿.服務器關閉線程池銷毀,服務器重啟會重新創建線程池,裏邊的線程也是新的;線程有這麽幾個特性:

  1.不知道什麽時候會被挑中執行,(線程並不遵循什麽先來先得,而是看優先級,另外也很隨機,可能先來的線程反而後執行)

  2.執行過程中會被隨時打斷,讓出CPU空間

  3.一旦出現硬盤、數據庫等這樣耗時的操作,也得讓出CPU去等待;

  4.就是數據來了,也不一定馬上執行,還得等著CPU挑選;

11.1.2 線程的運行流程和基本狀態:

新建線程---->就緒---->CPU---->阻塞----->就緒----->CPU----->阻塞----->就緒----->CPU......------>線程執行完畢,線程被kill;

線程產生後,會帶著自己要處理的數據和請求到達就緒序列,此時等待,如果優先級很高,那麽會很快執行,否則只能隨機被CPU抽中,CPU處理線程,當處理一段時間或者是線程中有進行IO流或者數據庫、硬盤等比較耗時的操作時,CPU會停止線程的運行,將線程運行的程度\地址等等保存,然後趕出CPU,此時線程到達阻塞序列,然後再到就緒序列,等待CPU再次執行,直到線程執行完,然後線程要麽被歸還到線程池,要麽線程被關閉,也就是kill.

上面其實就是線程的基本狀態:

Java線程具有五中基本狀態

新建狀態(New):當線程對象對創建後,即進入了新建狀態,如:Thread t = new MyThread();

就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處於就緒狀態的線程,只是說明此線程已經做好了準備,隨時等待CPU調度執行,並不是說執行了t.start()此線程立即就會執行;

運行狀態(Running):當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。註:就 緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;

阻塞狀態(Blocked):處於運行狀態中的線程由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:

1.等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;

2.同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態;

3.其他阻塞 -- 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。

死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

技術分享

11.1.3 Java中的線程

在Java中,“線程”指兩件不同的事情:
  1、java.lang.Thread類的一個實例;

  2、線程的執行。

  使用java.lang.Thread類或者java.lang.Runnable接口編寫代碼來定義、實例化和啟動新線程。一個Thread類實例只是一個對象,像Java中的任何其他對象一樣,具有變量和方法,生死於堆上。Java中,每個線程都有一個調用棧,即使不在程序中創建任何新的線程,線程也在後臺運行著。一個Java應用總是從main()方法開始運行,mian()方法運行在一個線程內,它被稱為主線程。一旦創建一個新的線程,就產生一個新的調用棧。線程總體分兩類:用戶線程User Thread和守護線程Daemon Thread當所有用戶線程執行完畢的時候,JVM自動關閉。但是守候線程卻不獨立於JVM,守候線程一般是由操作系統或者用戶自己創建的

11.1.4 多線程

11.2 多線程的定義與啟動

線程的定義有兩種方式,繼承java.lang.Thread類或者實現java.lang.Runnable接口,觀察API我們發現其實java.lang.Thread類也是實現了Runnable接口,由於java是單繼承的,不支持多繼承,所以我們往往為了改變這種限制,可以使用實現接口的方式來實現多線程技術;這也是最常用的方式.

11.2.1 定義線程

1、擴展java.lang.Thread類。

此類中有個run()方法,應該註意其用法:

public void run()

如果該線程是使用獨立的Runnable運行對象構造的,則調用該Runnable對象的run方法;否則,該方法不執行任何操作並返回。

Thread的子類應該重寫該方法。

2、實現java.lang.Runnable接口。

void run()

使用實現接口Runnable的對象創建一個線程時,啟動該線程將導致在獨立執行的線程中調用對象的run方法。

方法run的常規協定是,它可能執行任何所需的操作。

11.2.2 實例化線程

1、如果是擴展java.lang.Thread類的線程,則直接new即可。

2、如果是實現了java.lang.Runnable接口的類,則用Thread的構造方法:

Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

11.2.3 啟動線程

在線程的Thread對象上調用start()方法,而不是run()或者別的方法。

在調用start()方法之前:線程處於新狀態中,新狀態指有一個Thread對象,但還沒有一個真正的線程。在調用start()之前當前線程調用run()是同步的,不會產生非線程安全

在調用start()方法之後:發生了一系列復雜的事情

啟動新的執行線程(具有新的調用棧);

該線程從新狀態轉移到可運行狀態;

當該線程獲得機會執行時,其目標run()方法將運行。

註意:對Java來說,run()方法沒有任何特別之處。像main()方法一樣,它只是新線程知道調用的方法名稱(和簽名)。因此,在Runnable上或者Thread上調用run方法是合法的。但並不啟動新的線程。

package thread;
/*創建線程兩種方法:
* 方式一:  繼承Thread類
*     a: 自定義類 繼承 Thread類
*  b: 重寫run()方法
*  c: 創建自定義類對象
*  d: 調用start()方法
*/
public class MyThread extends Thread {
/*
 * 為什麽要繼承Thread 類?
 * 因為繼承Thread類, 自定義類 就是一個線程類
 * 
 * 為什麽要重寫run()方法呢?
 * 我們寫的多線程代碼,java設計者最初是不知道,提供給我們一個方法,這個方法就是用來執行多線程的代碼的
 * 這個方法就是run()方法, 這個方法內的代碼 就是 多線程需要執行的代碼
 * 
 * 為什麽執行線程的時候,調用的start() 不是 調用run()方法??
 *         run(): 方法的普通調用,run()方法內是線程要執行的代碼
 *         start(): 把該線程啟動, 自動調用run()方法
 * 
 */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+"--"+i);
        }
        super.run();
    }
    
    
}

實現接口方式實現多線程

package thread;
/*
 * 如何獲取到線程的名字呢?
 * 
 * public static Thread currentThread()返回對當前正在執行的線程對象的引用。
 */
public class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }
        
    }

}
package thread;
/*
 * 多線程:程序有多條執行路徑
 * 
 * 進程:就是運行的應用程序
 * 線程:應用程序的執行路徑
 * 單線程:程序只有一條執行路徑
 * 多線程:程序有多條執行路徑
 * 
 * 舉例:
 *         多線程: 迅雷下載
 * 
 *         單進程單線程: 一個人點一桌菜
 *         單進程多線程: 多個人點一桌菜
 *         多進程多線程:多個人點多桌菜
 *         
 *         
 * 創建一個多線程的程序
 * 首先需要創建一個進程,java語言不能直接操作系統,換句話來說,不能直接創建進程
 * java提供了一個類,這個類就可以解決進程相關的操作,這個類就是 線程類 ( Thread )
 * 
 * 通過查看API,發現創建線程有兩種方式:
 * 方式一:  繼承Thread類
 *     a: 自定義類 繼承 Thread類
 *  b: 重寫run()方法
 *  c: 創建自定義類對象
 *  d: 調用start()方法
 */
public class ThreadDemo{

    public static void main(String[] args) {
        //創建多線程
        threadTest();
        //獲取線程
        threadRunnableTest();

    }
    /*
     * 通過查看API,發現創建線程有兩種方式:
     * 方式一:  繼承Thread類
     *     a: 自定義類 繼承 Thread類
     *  b: 重寫run()方法
     *  c: 創建自定義類對象
     *  d: 調用start()方法  把該線程啟動, 自動調用run()方法
     *  
     *  線程中的方法:
     *      public final String getName(): 獲取線程的名字
     *          默認的名字: Thread-編號,  編號從0開始
     *  
     *      public final void setName(String name): 指定線程的名字
     */
    public static void threadTest(){
        // 創建自定義類對象
        // MyThread m = new MyThread();//創建了一個線程對象
        // m.run();
        // m.run();
        //創建自定義線程類對象
        MyThread mth1 = new MyThread();
        MyThread mth2 = new MyThread();
        mth2.setName("線程1");
        mth1.start();
        mth2.start();
        System.out.println("先運行本句輸出語句:從運行結果看,線程運行拋開優先級是沒有順序的是隨機的;");
    }
    /*
     * 創建線程的第二種方式: 實現 Runnable接口
     *  a: 自定義類 實現 Runnable接口
     *  b: 重寫run方法
     *  c: 創建自定義類對象
     *  d: 創建Thread類對象, 在創建的時候,把自定義類對象,做為構造函數的參數
     *  e: 調用start()方法
     *  
     *  構造函數
     *  public Thread(Runnable target)分配新的 Thread 對象
     *  public Thread(Runnable target,String name)分配新的 Thread 對象, 同時指定線程的名字
     */
    public static void threadRunnableTest(){
        //創建自定義類對象
        MyRunnable mth1 = new MyRunnable();
        //創建兩個線程對象
        Thread th1 = new Thread(mth1);
        Thread th2 = new Thread(mth1);
        //Thread th3 = new Thread(mth1,"線程2");
        th1.start();
        th2.start();
        System.out.println("獲取線程的名字:"+"qw");
        th1.stop();
    }
}

Thread類源代碼解析

測試代碼:
// 創建自定義類對象
MyRunnable m = new MyRunnable();

// 創建兩個線程對象
Thread t1 = new Thread(m);

t1.start();



源代碼
Class Thread {

    public Thread(Runnable target) {// target -- m -- MyRunnable類
        
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
                      
        //target -- m              
        init(g, target, name, stackSize, null);
    }
    
     private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
              
        //把自定義類中的對象m  賦值給 Thread類中的 target        
        this.target = target;  
    }
    
    @Override
    public void run() {
        if (target != null) {
        
            // m.run();
            target.run();
        }
    }



}

11.2.4 多線程的異步問題即非線程安全

  單任務的特點是排隊進行,也就是同步,但是單任務會使CPU的利用率大幅減低.而使用多線程則可以最大程度的利用CPU,使系統的利用率大大提升,但是多線程是異步的,使用多線程就是在使用異步.多線程存在共享數據的情況,共享數據就是多個線程可以訪問同一個變量,這會造成非線程安全.非線程安全主要指多個線程對同一個對象中的同一個實例變量進行操作時會出現值被更改\值不同步的情況,進而影響程序的執行流程.

11.2.4.1 買票問題-----synchronized鎖

package thread;

public class TicketThread implements Runnable{
    //定義票的張數
    private int ticket = 100;
    @Override
    public void run() {
        //多線程中執行的操作,通常是很耗時的,我們循環來模擬耗時的操作
        //賣票
        
        while (true) {
            //為了查看效果,加了睡眠操作
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(ticket>0){
                System.out.println("當前線程"+Thread.currentThread().getName()+"在賣第"+ticket--+"張票");
            }
        }
    }

}
package thread;
/*
 * 有100張票, 3個窗口在賣票,  模擬賣票操作
 * 
 * 在使用多線程的時候, 是使用第一種創建方式好(繼承Thread類), 還是第二種(實現Runnable接口) ??
 * java 是單繼承, 多實現, 所以 一般使用 第二種
 * 
 */
public class TicketDemo {

    public static void main(String[] args) {
        //賣票聯系
        threadTest();

    }
    //買票,三個窗口  三個線程
    public static void threadTest(){
        //創建自定義類對象
        TicketThread tt = new TicketThread();
        //創建線程對象
        Thread th1 = new Thread(tt, "窗口1");
        Thread th2 = new Thread(tt, "窗口2");
        Thread th3 = new Thread(tt, "窗口3");
        //開始賣票
        th1.start();
        th2.start();
        th3.start();
    }
}

當前線程窗口2在賣第100張票
當前線程窗口1在賣第99張票
當前線程窗口3在賣第98張票
當前線程窗口2在賣第97張票
當前線程窗口1在賣第96張票
當前線程窗口3在賣第95張票
當前線程窗口1在賣第94張票
當前線程窗口2在賣第93張票
當前線程窗口3在賣第92張票
當前線程窗口2在賣第91張票
當前線程窗口1在賣第90張票
當前線程窗口3在賣第89張票
當前線程窗口2在賣第88張票
當前線程窗口3在賣第87張票
當前線程窗口1在賣第86張票
當前線程窗口2在賣第85張票
當前線程窗口3在賣第84張票
當前線程窗口1在賣第83張票
當前線程窗口2在賣第82張票
當前線程窗口3在賣第81張票
當前線程窗口1在賣第80張票
當前線程窗口2在賣第79張票
當前線程窗口3在賣第78張票
當前線程窗口1在賣第77張票
當前線程窗口2在賣第76張票
當前線程窗口3在賣第75張票
當前線程窗口1在賣第74張票
當前線程窗口2在賣第73張票
當前線程窗口1在賣第72張票
當前線程窗口3在賣第71張票
當前線程窗口2在賣第70張票
當前線程窗口1在賣第69張票
當前線程窗口3在賣第68張票
當前線程窗口2在賣第67張票
當前線程窗口1在賣第66張票
當前線程窗口3在賣第65張票
當前線程窗口2在賣第64張票
當前線程窗口1在賣第63張票
當前線程窗口3在賣第62張票
當前線程窗口1在賣第60張票
當前線程窗口2在賣第61張票
當前線程窗口3在賣第59張票
當前線程窗口1在賣第57張票
當前線程窗口2在賣第58張票
當前線程窗口3在賣第56張票
當前線程窗口2在賣第55張票
當前線程窗口1在賣第54張票
當前線程窗口3在賣第53張票
當前線程窗口3在賣第52張票
當前線程窗口1在賣第50張票
當前線程窗口2在賣第51張票
當前線程窗口3在賣第49張票
當前線程窗口1在賣第48張票
當前線程窗口2在賣第48張票
當前線程窗口3在賣第47張票
當前線程窗口2在賣第46張票
當前線程窗口1在賣第45張票
當前線程窗口3在賣第44張票
當前線程窗口2在賣第43張票
當前線程窗口1在賣第42張票
當前線程窗口3在賣第41張票
當前線程窗口1在賣第40張票
當前線程窗口2在賣第39張票
當前線程窗口3在賣第38張票
當前線程窗口2在賣第37張票
當前線程窗口1在賣第36張票
當前線程窗口3在賣第35張票
當前線程窗口2在賣第34張票
當前線程窗口1在賣第33張票
當前線程窗口2在賣第32張票
當前線程窗口3在賣第31張票
當前線程窗口1在賣第30張票
當前線程窗口2在賣第29張票
當前線程窗口3在賣第28張票
當前線程窗口1在賣第27張票
當前線程窗口2在賣第26張票
當前線程窗口3在賣第25張票
當前線程窗口1在賣第24張票
當前線程窗口3在賣第23張票
當前線程窗口2在賣第22張票
當前線程窗口1在賣第21張票
當前線程窗口2在賣第20張票
當前線程窗口3在賣第20張票
當前線程窗口1在賣第19張票
當前線程窗口1在賣第18張票
當前線程窗口2在賣第17張票
當前線程窗口3在賣第18張票
當前線程窗口2在賣第16張票
當前線程窗口3在賣第15張票
當前線程窗口1在賣第14張票
當前線程窗口1在賣第13張票
當前線程窗口3在賣第12張票
當前線程窗口2在賣第11張票
當前線程窗口2在賣第10張票
當前線程窗口1在賣第9張票
當前線程窗口3在賣第8張票
當前線程窗口2在賣第7張票
當前線程窗口1在賣第6張票
當前線程窗口3在賣第5張票
當前線程窗口2在賣第4張票
當前線程窗口1在賣第3張票
當前線程窗口3在賣第2張票
當前線程窗口2在賣第1張票

從上面的代碼中我們看到,窗口2和窗口3賣出了共同的一張票這是為什麽呢,由於一個線程在操作時,輸出語句中有--操作,線程進行到這裏沒有執行--就被掛起了,也就是已經賣出票了,但是沒有將庫存票數減1,另一個線程進入然後執行完輸出賣出第20張票,此時掛起的線程進來繼續剩下的,所以看到輸出了兩個20 ;這是多線程非線程安全的典型例子,那麽該怎麽解決呢,這裏就引入了鎖的概念,我們可以在一個線程運行時,將其鎖起來,給這個鎖一個名字,其他線程進來,獲取鎖,如果不能獲取到鎖那麽就意味著裏邊有一個線程在執行,此時這個線程會不停的申請鎖,直到能夠拿到為止,而且是多個線程在申請這把鎖,這裏的鎖其實是同步代碼塊和同步方法,關鍵字為synchronized,下面通過同步代碼塊和同步方法來實現

package cn.itcast_05;

/*
 * 票
 * 
 * public static void sleep(long millis) 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),
 */
public class Ticket implements Runnable {
    
    //100票
    private int ticket = 100;

//    @Override
//    public void run() {
//        
//        //多個窗口賣同一張票
//        //t1,t2,t3
//        //賣票
//        while(true){
//            
//            //t1,t2,t3
//            
//            //模擬休息一會
//            try {
//                Thread.sleep(10);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            
//            //t1,t2,t3
//            //當前50票
//            if(ticket > 0){
//                System.out.println( Thread.currentThread().getName() +"正在賣第 "+ ticket-- +" 張票");
//                //ticket-- 這句話, 其實 做了 2個事件
//                //第一個操作 賣了當前的這張票ticket 50
//                //第二個操作,票數-1, 變成了 49
//                
//                //t1,窗口1正在賣第50票,還沒有做  ticket--操作, t2進來了,t1失去了CPU執行權
//                //t2,窗口2正在賣第50票,之後  ticket--操作, 票 49
//                
//            }
//        }
//    }
    
    @Override
    public void run() {
        
        //票出現了負數
        //賣票
        while(true){
            
            //t1,t2,t3
            //模擬休息一會
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            //t1,t2,t3
            //最後一張票
            if(ticket > 0){
                //t1,
                System.out.println( Thread.currentThread().getName() +"正在賣第 "+ ticket-- +" 張票");
                //t1, 窗口1 正在賣第1張票, 在做 票--之前, t2進來了,t2發現還有一張票,t2可以出票
                //t2
                //t3
                // t1票--  1-->0
                //t2, 窗口2 正在賣第0張票,
                // t2票--  0--> -1
                //t3, 窗口3 正在賣第 -1 張票
                // t3票--  -1 --> -2
            }
        }
    }

}
package cn.itcast_05;
/*
 * 有100張票, 3個窗口在賣票,  模擬賣票操作
 * 
 * 在使用多線程的時候, 是使用第一種創建方式好(繼承Thread類), 還是第二種(實現Runnable接口) ??
 * java 是單繼承, 多實現, 所以 一般使用 第二種
 * 
 * 發現模擬真實的情況進行賣票,出現了問題:
 * 
 *     a: 多個窗口賣同一張票
 *         如果多線程中的代碼很簡單,是不會出現問題的
 *         如果,當前的多線程代碼中 有類似與 如下的代碼    SOP( ticket-- );
 *             因為當前的這個操作, 它實際做了一個以上的操作,那麽就在第一個操作完畢,而第二個操作沒執行之前, 另外一個線程進來執行了 
 *     b: 出現了負數的票
 *         多個線程,同時通過判斷條件
 */
public class TicketDemo {
    public static void main(String[] args) {
        //創建票對象
        Ticket t = new Ticket();
        
        //創建3個線程對象
        Thread t1 = new Thread(t, "窗口1:");
        Thread t2 = new Thread(t, "窗口2:");
        Thread t3 = new Thread(t, "窗口3:");
        
        t1.start();
        t2.start();
        t3.start();
    }
}
package cn.itcast_06;

public class Ticket implements Runnable {
    
    //100票
    private int ticket = 100;
    //鎖對象
    private Object obj = new Object();
    
    @Override
    public void run() {
        
        //賣票
        while(true){
            
            synchronized (obj){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                if(ticket > 0){
                    System.out.println( Thread.currentThread().getName() +"正在賣第 "+ ticket-- +" 張票");
                }
            }
        }
    }

}
package cn.itcast_06;
/*
 * 有100張票, 3個窗口在賣票,  模擬賣票操作
 * 
 * 在使用多線程的時候, 是使用第一種創建方式好(繼承Thread類), 還是第二種(實現Runnable接口) ??
 * java 是單繼承, 多實現, 所以 一般使用 第二種
 * 
 * 發現模擬真實的情況進行賣票,出現了問題:
 * 
 *     a: 多個窗口賣同一張票
 *         如果多線程中的代碼很簡單,是不會出現問題的
 *         如果,當前的多線程代碼中 有類似與 如下的代碼    SOP( ticket-- );
 *             因為當前的這個操作, 它實際做了一個以上的操作,那麽就在第一個操作完畢,而第二個操作沒執行之前, 另外一個線程進來執行了 
 *     b: 出現了負數的票
 *         多個線程,同時通過判斷條件
 * 
 *  為什麽會出現問題?找問題
 *  當前多線程的程序
 *  是否有共享的內容
 *  這個共享的內容,把操作一次以上
 *  
 *  這個問題其實可以通過  鎖機制來解決,在java中這種鎖機制有另外一個稱呼: 同步機制
 *  可以 同步代碼塊來解決該問題
 *      格式 :
 *      synchronized (鎖對象){
 *          需要被鎖的內容
 *      }
 *  
 *      註意: 多個線程對象 必須使用同一個鎖對象
 */
public class TicketDemo {
    public static void main(String[] args) {
        //創建票對象
        Ticket t = new Ticket();
        
        //創建3個線程對象
        Thread t1 = new Thread(t, "窗口1:");
        Thread t2 = new Thread(t, "窗口2:");
        Thread t3 = new Thread(t, "窗口3:");
        
        t1.start();
        t2.start();
        t3.start();
    }
}
package cn.itcast_07;

public class Ticket implements Runnable {
    
    //100票
    private static int ticket = 100;
    //鎖對象
    private Object obj = new Object();
    private int x = 0;
    
    @Override
    public void run() {
        
        //賣票
        while(true){
            
            if (x%2 == 0) {
                //synchronized (this){//普通方法
                synchronized (Ticket.class){//靜態方法
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                    if(ticket > 0){
                        System.out.println( Thread.currentThread().getName() +"正在賣第 "+ ticket-- +" 張票(if)");
                    }
                }
                
            } else{
                
                Ticket.sendTicket();
            }
            
            x++;
        }
    }
    
    //賣票
//    synchronized public void sendTicket(){
//            try {
//                Thread.sleep(10);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            
//            if(ticket > 0){
//                System.out.println( Thread.currentThread().getName() +"正在賣第 "+ ticket-- +" 張票(else)");
//            }
//    }
    
    //靜態方法
    public static synchronized void sendTicket(){
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        if(ticket > 0){
            System.out.println( Thread.currentThread().getName() +"正在賣第 "+ ticket-- +" 張票(else)");
        }
}

}
package cn.itcast_07;
/*
 * 有100張票, 3個窗口在賣票,  模擬賣票操作
 * 
 * 在使用多線程的時候, 是使用第一種創建方式好(繼承Thread類), 還是第二種(實現Runnable接口) ??
 * java 是單繼承, 多實現, 所以 一般使用 第二種
 * 
 * 發現模擬真實的情況進行賣票,出現了問題:
 * 
 *     a: 多個窗口賣同一張票
 *         如果多線程中的代碼很簡單,是不會出現問題的
 *         如果,當前的多線程代碼中 有類似與 如下的代碼    SOP( ticket-- );
 *             因為當前的這個操作, 它實際做了一個以上的操作,那麽就在第一個操作完畢,而第二個操作沒執行之前, 另外一個線程進來執行了 
 *     b: 出現了負數的票
 *         多個線程,同時通過判斷條件
 * 
 *  為什麽會出現問題?找問題
 *  當前多線程的程序
 *  是否有共享的內容
 *  這個共享的內容,把操作一次以上
 *  
 *  這個問題其實可以通過  鎖機制來解決,在java中這種鎖機制有另外一個稱呼: 同步機制
 *  可以 同步代碼塊來解決該問題
 *      格式 :
 *      synchronized (鎖對象){
 *          需要被鎖的內容
 *      }
 *  
 *      註意: 多個線程對象 必須使用同一個鎖對象
 *  
 *  
 *  方法內的數據 全部為鎖上了,這個時候,我們就可以考慮直接給方法上鎖
 *  同步方法: 就是把鎖的關鍵字 放在方法上   synchronized
 *  同步方法的鎖是誰?
 *      this
 *  靜態修飾的同步方法,鎖是誰呢?
 *      該靜態方法所屬類的  字節碼文件對象  class
 *  
 *      public final Class<?> getClass()返回此 Object 的運行時類  ,字節碼文件對象
 *       
 *       Ticket 
 *       數據類型 .class 就可以獲取到當前數據類型所對應的字節碼文件對象 
 *  
 */
public class TicketDemo {
    public static void main(String[] args) {
        //創建票對象
        Ticket t = new Ticket();
        
        //創建3個線程對象
        Thread t1 = new Thread(t, "窗口1:");
        Thread t2 = new Thread(t, "窗口2:");
        Thread t3 = new Thread(t, "窗口3:");
        
        t1.start();
        t2.start();
        t3.start();
    }
}

11.2.4.2 買票問題----Lock鎖

  

11.多線程&&並發