No_16_0325 Java基礎學習第二十四天—多線程學習總結

文檔版本 開發(fā)工具 測試平臺 工程名字 日期 作者 備注
V1.0 2016.03.25 lutianfei none

[TOC]


第十章 多線程

多線程概述

什么是進程?

  • 進程:就是正在運行的程序苞笨。
  • 進程是系統(tǒng)進行資源分配和調(diào)用的獨立單位腻贰。每一個進程都有它自己的內(nèi)存空間和系統(tǒng)資源。

多進程有什么意義呢?

  • 可以在一個時間段內(nèi)執(zhí)行多個任務。
  • 可以提高CPU的使用率。

什么是線程呢?

  • 在同一個進程內(nèi)又可以執(zhí)行多個任務,而這每一個任務我就可以看出是一個線程
  • 線程:是程序的執(zhí)行單元執(zhí)行路徑若皱。是程序使用CPU的最基本單位
  • 單線程:如果程序只有一條執(zhí)行路徑尘颓。
  • 多線程:如果程序有多條執(zhí)行路徑走触。

多線程有什么意義呢?

  • 多線程的存在,不是提高程序的執(zhí)行速度泥耀。其實是為了提高應用程序的使用率饺汹。
  • 程序的執(zhí)行其實都是在搶CPU的資源,CPU的執(zhí)行權痰催。
  • 多個進程是在搶這個資源,而其中的某一個進程如果執(zhí)行路徑比較多迎瞧,就會有更高的幾率搶到CPU的執(zhí)行權夸溶。
  • 我們是不敢保證哪一個線程能夠在哪個時刻搶到,所以線程的執(zhí)行有隨機性凶硅。

什么是并發(fā)呢缝裁?

  • 大家注意兩個詞匯的區(qū)別:并行并發(fā)
  • 并行邏輯上同時發(fā)生足绅,指在某一個時間內(nèi)同時運行多個程序捷绑。
  • 并發(fā)物理上同時發(fā)生,指在某一個時間點同時運行多個程序氢妈。
  • 我們可以實現(xiàn)真正意義上的并發(fā),例如:多個CPU就可以實現(xiàn)粹污,不過得知道如何調(diào)度和控制它們。

Java程序運行原理

  • java 命令會啟動 java 虛擬機首量,啟動 JVM壮吩,等于啟動了一個應用程序进苍,也就是啟動了一個進程。該進程會自動啟動一個 “主線程” 鸭叙,然后主線程去調(diào)用某個類的 main 方法觉啊。所以 main方法運行在主線程中。在此之前的所有程序都是單線程的沈贝。

  • 思考:jvm虛擬機的啟動是單線程的還是多線程的杠人?

    • JVM啟動至少啟動了垃圾回收線程主線程,所以是多線程的宋下。

如何實現(xiàn)多線程

  • 由于線程是依賴進程而存在的嗡善,所以我們應該先創(chuàng)建一個進程出來。進程是由系統(tǒng)創(chuàng)建的杨凑,所以我們應該去調(diào)用系統(tǒng)功能創(chuàng)建一個進程滤奈。
  • Java是不能直接調(diào)用系統(tǒng)功能的,所以撩满,我們沒有辦法直接實現(xiàn)多線程程序蜒程。但是Java可以去調(diào)用C/C++寫好的程序來實現(xiàn)多線程程序。
  • 由C/C++去調(diào)用系統(tǒng)功能創(chuàng)建進程伺帘,然后由Java去調(diào)用這樣的東西昭躺,然后提供一些類供我們使用。我們就可以實現(xiàn)多線程程序了伪嫁。

多線程的實現(xiàn)方案1

  • 方式1:繼承Thread類领炫。

  • 步驟:

    • A:自定義類MyThread繼承Thread類。
    • B:MyThread類里面重寫run()
    • C:創(chuàng)建對象
    • D:啟動線程
  • 面試題:run()和start()的區(qū)別?

    • run():僅僅是封裝被線程執(zhí)行的代碼张咳,直接調(diào)用是普通方法
    • start():首先啟動了線程帝洪,然后再由jvm去調(diào)用該線程的run()方法。
  • 面試題:為什么重寫run()方法?`

    • 不是類中的所有代碼都需要被線程執(zhí)行的脚猾。為了區(qū)分哪些代碼能夠被線程執(zhí)行葱峡,java提供了Thread類中的run()用來包含那些被線程執(zhí)行的代碼。
/*
 * 需求:我們要實現(xiàn)多線程的程序龙助。
 */
public class MyThreadDemo {
    public static void main(String[] args) {
        // 創(chuàng)建線程對象
        // MyThread my = new MyThread();
        // // 啟動線程
        // my.run();
        // my.run();
        // 調(diào)用run()方法為什么是單線程的呢?
        // 因為run()方法直接調(diào)用其實就相當于普通的方法調(diào)用,所以你看到的是單線程的效果
        // 要想看到多線程的效果砰奕,就必須說說另一個方法:start()
        // 面試題:run()和start()的區(qū)別?
        // run():僅僅是封裝被線程執(zhí)行的代碼,直接調(diào)用是普通方法
        // start():首先啟動了線程提鸟,然后再由jvm去調(diào)用該線程的run()方法军援。
        // MyThread my = new MyThread();
        // my.start();
        // // IllegalThreadStateException:非法的線程狀態(tài)異常
        // // 為什么呢?因為這個相當于是my線程被調(diào)用了兩次。而不是兩個線程啟動称勋。
        // my.start();

        // 創(chuàng)建兩個線程對象
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        my1.start();
        my2.start();
    }
}


public class MyThread extends Thread {

    @Override
    public void run() {
        // 自己寫代碼
        // System.out.println("好好學習胸哥,天天向上");
        // 一般來說,被線程執(zhí)行的代碼肯定是比較耗時的铣缠。所以我們用循環(huán)改進
        for (int x = 0; x < 200; x++) {
            System.out.println(x);
        }
    }

}


如何獲取和設置線程名稱

  • Thread類的基本獲取和設置方法

    • public final String getName():獲取線程的名稱烘嘱。
    • public final void setName(String name):設置線程的名稱
    • 通過構造方法也可以給線程起名字
  • 思考:

    • 如何獲取main方法所在的線程名稱呢?
    • public static Thread currentThread()
      • 這樣就可以獲取任意方法所在的線程名稱
/*
 * 針對不是Thread類的子類中如何獲取線程對象名稱呢?
 * public static Thread currentThread():返回當前正在執(zhí)行的線程對象
 * Thread.currentThread().getName()
 */
public class MyThreadDemo {
    public static void main(String[] args) {
        // 創(chuàng)建線程對象
        //無參構造+setXxx()
        // MyThread my1 = new MyThread();
        // MyThread my2 = new MyThread();
        // //調(diào)用方法設置名稱
        // my1.setName("林青霞");
        // my2.setName("劉意");
        // my1.start();
        // my2.start();
        
        //帶參構造方法給線程起名字
        // MyThread my1 = new MyThread("林青霞");
        // MyThread my2 = new MyThread("劉意");
        // my1.start();
        // my2.start();
        
        //我要獲取main方法所在的線程對象的名稱昆禽,該怎么辦呢?
        //遇到這種情況,Thread類提供了一個很好玩的方法:
        //public static Thread currentThread():返回當前正在執(zhí)行的線程對象
        System.out.println(Thread.currentThread().getName());
    }
}

/*
名稱為什么是:Thread-? 編號

class Thread {
    private char name[];

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }
    
     private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        //大部分代碼被省略了
        this.name = name.toCharArray();
    }
    
    public final void setName(String name) {
        this.name = name.toCharArray();
    }
    
    
    private static int threadInitNumber; //0,1,2
    private static synchronized int nextThreadNum() {
        return threadInitNumber++; //return 0,1
    }
    
    public final String getName() {
        return String.valueOf(name);
    }
}

class MyThread extends Thread {
    public MyThread() {
        super();
    }
}

*/



public class MyThread extends Thread {

    public MyThread() {
    }
    
    public MyThread(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
        }
    }
}



線程調(diào)度

  • 假如我們的計算機只有一個 CPU,那么 CPU 在某一個時刻只能執(zhí)行一條指令蝇庭,線程只有得到CPU時間片醉鳖,也就是使用權,才可以執(zhí)行指令哮内。那么Java是如何對線程進行調(diào)用的呢盗棵?
  • 線程有兩種調(diào)度模型:
    • 分時調(diào)度模型 所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間片
    • 搶占式調(diào)度模型 優(yōu)先讓優(yōu)先級高的線程使用 CPU北发,如果線程的優(yōu)先級相同纹因,那么會隨機選擇一個,優(yōu)先級高的線程獲取的 CPU 時間片相對多一些琳拨。
    • Java使用的是搶占式調(diào)度模型瞭恰。
      • public final int getPriority():返回線程對象的優(yōu)先級
      • public final void setPriority(int newPriority):更改線程的優(yōu)
      • 線程默認優(yōu)先級是5
      • 線程優(yōu)先級的范圍是:1-10狱庇。
      • 線程優(yōu)先級高僅僅表示線程獲取的 CPU時間片的幾率高惊畏,但是要在次數(shù)比較多,或者多次運行的時候才能看到比較好的效果密任。
public class ThreadPriority extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
        }
    }
}




/*
 * 注意:
 * IllegalArgumentException:非法參數(shù)異常颜启。
 * 拋出的異常表明向方法傳遞了一個不合法或不正確的參數(shù)。 
 *
 */
public class ThreadPriorityDemo {
    public static void main(String[] args) {
        ThreadPriority tp1 = new ThreadPriority();
        ThreadPriority tp2 = new ThreadPriority();
        ThreadPriority tp3 = new ThreadPriority();

        tp1.setName("東方不敗");
        tp2.setName("岳不群");
        tp3.setName("林平之");

        // 獲取默認優(yōu)先級
        // System.out.println(tp1.getPriority());
        // System.out.println(tp2.getPriority());
        // System.out.println(tp3.getPriority());

        // 設置線程優(yōu)先級
        // tp1.setPriority(100000);
        
        //設置正確的線程優(yōu)先級
        tp1.setPriority(10);
        tp2.setPriority(1);

        tp1.start();
        tp2.start();
        tp3.start();
    }
}


線程控制

  • 我們已經(jīng)知道了線程的調(diào)度浪讳,接下來我們就可以使用如下方法對象線程進行控制

  • 線程休眠

    • public static void sleep(long millis)
  • 線程加入

    • public final void join()
  • 線程禮讓

    • public static void yield()
  • 后臺線程

    • public final void setDaemon(boolean on)
  • 中斷線程

    • public final void stop()
    • public void interrupt()
  • 案例:線程休眠

public class ThreadSleep extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x + ",日期:" + new Date());
            // 睡眠
            // 困了缰盏,我稍微休息1秒鐘
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}




/*
 * 線程休眠
 *        public static void sleep(long millis)
 */
public class ThreadSleepDemo {
    public static void main(String[] args) {
        ThreadSleep ts1 = new ThreadSleep();
        ThreadSleep ts2 = new ThreadSleep();
        ThreadSleep ts3 = new ThreadSleep();

        ts1.setName("林青霞");
        ts2.setName("林志玲");
        ts3.setName("林志穎");

        ts1.start();
        ts2.start();
        ts3.start();
    }
}


  • 案例:線程加入
public class ThreadJoin extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
        }
    }
}



/*
 * public final void join():等待該線程終止。 
 */
public class ThreadJoinDemo {
    public static void main(String[] args) {
        ThreadJoin tj1 = new ThreadJoin();
        ThreadJoin tj2 = new ThreadJoin();
        ThreadJoin tj3 = new ThreadJoin();

        tj1.setName("李淵");
        tj2.setName("李世民");
        tj3.setName("李元霸");

        tj1.start();
        try {
            tj1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        tj2.start();
        tj3.start();
    }
}


  • 案例:線程禮讓
public class ThreadYield extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
            Thread.yield();
        }
    }
}



/*
 * public static void yield():暫停當前正在執(zhí)行的線程對象淹遵,并執(zhí)行其他線程口猜。 
 * 讓多個線程的執(zhí)行更和諧,但是不能靠它保證一人一次透揣。
 */
public class ThreadYieldDemo {
    public static void main(String[] args) {
        ThreadYield ty1 = new ThreadYield();
        ThreadYield ty2 = new ThreadYield();

        ty1.setName("林青霞");
        ty2.setName("劉意");

        ty1.start();
        ty2.start();
    }
}


  • 案例:后臺線程
public class ThreadDaemon extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
        }
    }
}


/*
 * public final void setDaemon(boolean on):將該線程標記為守護線程或用戶線程暮的。
 * 當正在運行的線程都是守護線程時,Java 虛擬機退出淌实。 該方法必須在啟動線程前調(diào)用。 
 * 
 * 游戲:坦克大戰(zhàn)猖腕。
 */
public class ThreadDaemonDemo {
    public static void main(String[] args) {
        ThreadDaemon td1 = new ThreadDaemon();
        ThreadDaemon td2 = new ThreadDaemon();

        td1.setName("關羽");
        td2.setName("張飛");

        // 設置收獲線程
        td1.setDaemon(true);
        td2.setDaemon(true);

        td1.start();
        td2.start();

        Thread.currentThread().setName("劉備");
        for (int x = 0; x < 5; x++) {
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }
}


  • 案例:中斷線程
public class ThreadStop extends Thread {
    @Override
    public void run() {
        System.out.println("開始執(zhí)行:" + new Date());

        // 我要休息10秒鐘拆祈,親,不要打擾我哦
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            // e.printStackTrace();
            System.out.println("線程被終止了");
        }

        System.out.println("結束執(zhí)行:" + new Date());
    }
}



/*
 * public final void stop():讓線程停止倘感,過時了放坏,但是還可以使用。
 * public void interrupt():中斷線程老玛。 把線程的狀態(tài)終止淤年,并拋出一個InterruptedException钧敞。
 */
public class ThreadStopDemo {
    public static void main(String[] args) {
        ThreadStop ts = new ThreadStop();
        ts.start();

        // 你超過三秒不醒過來,我就干死你
        try {
            Thread.sleep(3000);
            // ts.stop();
            ts.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


線程的生命周期圖

多線程的實現(xiàn)方案2

  • 實現(xiàn)Runnable接口
    • 步驟:
      • A:自定義類MyRunnable實現(xiàn)Runnable接口
      • B:重寫run()方法
      • C:創(chuàng)建MyRunnable類的對象
      • D:創(chuàng)建Thread類的對象麸粮,并把C步驟的對象作為構造參數(shù)傳遞
  • 實現(xiàn)接口方式的好處
    • 可以避免由于Java單繼承帶來的局限性溉苛。
    • 適合多個相同程序的代碼去處理同一個資源的情況,把線程同程序的代碼弄诲,數(shù)據(jù)有效分離愚战,較好的體現(xiàn)了面向?qū)ο蟮脑O計思想。
public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            // 由于實現(xiàn)接口的方式就不能直接使用Thread類的方法了,但是可以間接的使用
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }

}

/*
 * 方式2:實現(xiàn)Runnable接口
 */
public class MyRunnableDemo {
    public static void main(String[] args) {
        // 創(chuàng)建MyRunnable類的對象
        MyRunnable my = new MyRunnable();

        // 創(chuàng)建Thread類的對象齐遵,并把C步驟的對象作為構造參數(shù)傳遞
        // Thread(Runnable target)
        // Thread t1 = new Thread(my);
        // Thread t2 = new Thread(my);
        // t1.setName("林青霞");
        // t2.setName("劉意");

        // Thread(Runnable target, String name)
        Thread t1 = new Thread(my, "林青霞");
        Thread t2 = new Thread(my, "劉意");

        t1.start();
        t2.start();
    }
}


多線程程序練習

  • 需求:
    • 某電影院目前正在上映賀歲大片寂玲,共有100張票,而它有3個售票窗口售票梗摇,請設計一個程序模擬該電影院售票拓哟。
    • 兩種方式實現(xiàn)
      • 繼承Thread類
      • 實現(xiàn)Runnable接口
  • 方式一:
public class SellTicket extends Thread {

    // 定義100張票
    // private int tickets = 100;
    // 為了讓多個線程對象共享這100張票,我們其實應該用靜態(tài)修飾
    private static int tickets = 100;

    @Override
    public void run() {
        // 定義100張票
        // 每個線程進來都會走這里伶授,這樣的話断序,每個線程對象相當于買的是自己的那100張票,這不合理谎砾,所以應該定義到外面
        // int tickets = 100;

        // 是為了模擬一直有票
        while (true) {
            if (tickets > 0) {
                System.out.println(getName() + "正在出售第" + (tickets--) + "張票");
            }
        }
    }
}




/*
 * 某電影院目前正在上映賀歲大片(紅高粱,少林寺傳奇藏經(jīng)閣)逢倍,共有100張票,而它有3個售票窗口售票景图,請設計一個程序模擬該電影院售票较雕。
 * 繼承Thread類來實現(xiàn)。
 */
public class SellTicketDemo {
    public static void main(String[] args) {
        // 創(chuàng)建三個線程對象
        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();
    }
}


  • 方式二:
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--) + "張票");
            }
        }
    }
}



/*
 * 實現(xiàn)Runnable接口的方式實現(xiàn)
 */
public class SellTicketDemo {
    public static void main(String[] args) {
        // 創(chuàng)建資源對象
        SellTicket st = new SellTicket();

        // 創(chuàng)建三個線程對象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 啟動線程
        t1.start();
        t2.start();
        t3.start();
    }
}


關于電影院賣票程序的思考

  • 我們前面講解過電影院售票程序挚币,從表面上看不出什么問題亮蒋,但是在真實生活中,售票時網(wǎng)絡是不能實時傳輸?shù)淖北希偸谴嬖谘舆t的情況慎玖,所以,在出售一張票以后笛粘,需要一點時間的延遲
    • 改實現(xiàn)接口方式的賣票程序
      • 每次賣票延遲100毫秒

改進后的電影院售票出現(xiàn)問題

  • 問題
    • 相同的票出現(xiàn)多次
      • CPU的一次操作必須是原子性的
    • 還出現(xiàn)了負數(shù)的票
      • 隨機性和延遲導致的
  • 注意
    • 線程安全問題在理想狀態(tài)下趁怔,不容易出現(xiàn),但一旦出現(xiàn)對軟件的影響是非常大的薪前。
public class SellTicket implements Runnable {
    // 定義100張票
    private int tickets = 100;

//    @Override
//    public void run() {
//        while (true) {
//            // t1,t2,t3三個線程
//            // 這一次的tickets = 100;
//            if (tickets > 0) {
//                // 為了模擬更真實的場景润努,我們稍作休息
//                try {
//                    Thread.sleep(100); // t1就稍作休息,t2就稍作休息
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//
//                System.out.println(Thread.currentThread().getName() + "正在出售第"
//                        + (tickets--) + "張票");
//                // 理想狀態(tài):
//                // 窗口1正在出售第100張票
//                // 窗口2正在出售第99張票
//                // 但是呢?
//                // CPU的每一次執(zhí)行必須是一個原子性(最簡單基本的)的操作。
//                // 先記錄以前的值
//                // 接著把ticket--
//                // 然后輸出以前的值(t2來了)
//                // ticket的值就變成了99
//                // 窗口1正在出售第100張票
//                // 窗口2正在出售第100張票
//
//            }
//        }
//    }
    
    @Override
    public void run() {
        while (true) {
            // t1,t2,t3三個線程
            // 這一次的tickets = 1;
            if (tickets > 0) {
                // 為了模擬更真實的場景示括,我們稍作休息
                try {
                    Thread.sleep(100); //t1進來了并休息铺浇,t2進來了并休息,t3進來了并休息垛膝,
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + "正在出售第"
                        + (tickets--) + "張票");
                //窗口1正在出售第1張票,tickets=0
                //窗口2正在出售第0張票,tickets=-1
                //窗口3正在出售第-1張票,tickets=-2
            }
        }
    }
}


解決線程安全問題的基本思想

  • 首先想為什么出現(xiàn)問題?(也是我們判斷是否有問題的標準)
    • 是否是多線程環(huán)境
    • 是否有共享數(shù)據(jù)
    • 是否有多條語句操作共享數(shù)據(jù)
  • 如何解決多線程安全問題呢?
    • 基本思想:讓程序沒有安全問題的環(huán)境鳍侣。
      • 把多個語句操作共享數(shù)據(jù)的代碼給鎖起來丁稀,讓任意時刻只能有一個線程執(zhí)行即可。

同步的特點

  • 同步的前提
    • 多個線程
    • 多個線程使用的是同一個鎖對象
  • 同步的好處
    • 同步的出現(xiàn)解決了多線程的安全問題倚聚。
  • 同步的弊端
    • 當線程相當多時线衫,因為每個線程都會去判斷同步上的鎖,這是很耗費資源的秉沼,無形中會降低程序的運行效率桶雀。

解決線程安全問題實現(xiàn)1

  • 同步代碼塊
  • 格式:
    • synchronized(對象){需要同步的代碼;}
  • 同步可以解決安全問題的根本原因就在那個對象上。該對象如同鎖的功能唬复。
  • 同步代碼塊的對象可以是哪些呢?
public class SellTicket implements Runnable {
    // 定義100張票
    private int tickets = 100;
    //創(chuàng)建鎖對象
    private Object obj = new Object();


    @Override
        public void run() {
        while (true) {
            // t1,t2,t3都能走到這里
            // 假設t1搶到CPU的執(zhí)行權矗积,t1就要進來
            // 假設t2搶到CPU的執(zhí)行權,t2就要進來,發(fā)現(xiàn)門是關著的敞咧,進不去棘捣。所以就等著。
            // 門(開,關)
            synchronized (obj) { // 發(fā)現(xiàn)這里的代碼將來是會被鎖上的休建,所以t1進來后乍恐,就鎖了。(關)
                if (tickets > 0) {
                    try {
                        Thread.sleep(100); // t1就睡眠了
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "張票 ");
                    //窗口1正在出售第100張票
                }
            } //t1就出來可测砂,然后就開門茵烈。(開)
        }
    }
}




/*
 * 如何解決線程安全問題呢?
 * 注意:
 * 同步可以解決安全問題的根本原因就在那個對象上。該對象如同鎖的功能砌些。
 * 多個線程必須是同一把鎖呜投。
 */
public class SellTicketDemo {
    public static void main(String[] args) {
        // 創(chuàng)建資源對象
        SellTicket st = new SellTicket();

        // 創(chuàng)建三個線程對象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 啟動線程
        t1.start();
        t2.start();
        t3.start();
    }
}


解決線程安全問題實現(xiàn)2:同步方法

  • 就是把同步關鍵字加到方法上
  • 同步方法的鎖對象是什么呢?
    • this
  • 如果是靜態(tài)方法,同步方法的鎖對象又是什么呢?
    • 類的字節(jié)碼文件對象存璃。
  • 那么仑荐,我們到底使用誰?
    • 如果鎖對象是this,就可以考慮使用同步方法纵东。
    • 否則能使用同步代碼塊的盡量使用同步代碼塊粘招。
同步解決線程安全問題總結
  • A:同步代碼塊
    • synchronized(對象) {
      需要被同步的代碼;
      }
    • 這里的鎖對象可以是任意對象
  • B:同步方法
    • 把同步關鍵字加在方法上偎球。
    • 這里的鎖對象是this
  • C:靜態(tài)同步方法
    • 把同步關鍵字加在方法上洒扎。
    • 鎖對象是類的字節(jié)碼文件對象。(反射會講)
public class SellTicket implements Runnable {

    // 定義100張票
    private static int tickets = 100;

    // 定義同一把鎖
    private Object obj = new Object();
    private Demo d = new Demo();

    private int x = 0;

    //同步代碼塊用obj做鎖
//    @Override
//    public void run() {
//        while (true) {
//            synchronized (obj) {
//                if (tickets > 0) {
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName()
//                            + "正在出售第" + (tickets--) + "張票 ");
//                }
//            }
//        }
//    }
    
    //同步代碼塊用任意對象做鎖
//    @Override
//    public void run() {
//        while (true) {
//            synchronized (d) {
//                if (tickets > 0) {
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName()
//                            + "正在出售第" + (tickets--) + "張票 ");
//                }
//            }
//        }
//    }

    @Override
    public void run() {
        while (true) {
            if(x%2==0){
                synchronized (SellTicket.class) {//靜態(tài)方法的安全鎖衰絮,若是普通同步方法則為:this
                    if (tickets > 0) {
                        try {
                            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 synchronized void sellTicket() {
//            if (tickets > 0) {
//            try {
//                    Thread.sleep(100);
//            } catch (InterruptedException e) {
//                    e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName()
//                        + "正在出售第" + (tickets--) + "張票 ");
//            }
//    }


//靜態(tài)同步方法:
    private static synchronized void sellTicket() {
        if (tickets > 0) {
        try {
                Thread.sleep(100);
        } catch (InterruptedException e) {
                e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()
                    + "正在出售第" + (tickets--) + "張票 ");
        }
}
}

class Demo {
}





public class SellTicketDemo {
    public static void main(String[] args) {
        // 創(chuàng)建資源對象
        SellTicket st = new SellTicket();

        // 創(chuàng)建三個線程對象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 啟動線程
        t1.start();
        t2.start();
        t3.start();
    }
}


常見線程安全集合

public class ThreadDemo {
    public static void main(String[] args) {
        // 線程安全的類
        StringBuffer sb = new StringBuffer();
        Vector<String> v = new Vector<String>();
        Hashtable<String, String> h = new Hashtable<String, String>();

        // Vector是線程安全的時候才去考慮使用的,但是我還說過即使要安全岂傲,我也不用你
        // 那么到底用誰呢?
        // public static <T> List<T> synchronizedList(List<T> list)
        List<String> list1 = new ArrayList<String>();// 線程不安全
        List<String> list2 = Collections
                .synchronizedList(new ArrayList<String>()); // 線程安全
    }
}


最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市子檀,隨后出現(xiàn)的幾起案子镊掖,更是在濱河造成了極大的恐慌乃戈,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亩进,死亡現(xiàn)場離奇詭異症虑,居然都是意外死亡,警方通過查閱死者的電腦和手機归薛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門谍憔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人主籍,你說我怎么就攤上這事习贫。” “怎么了千元?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵苫昌,是天一觀的道長。 經(jīng)常有香客問我幸海,道長祟身,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任物独,我火速辦了婚禮袜硫,結果婚禮上,老公的妹妹穿的比我還像新娘挡篓。我一直安慰自己婉陷,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布瞻凤。 她就那樣靜靜地躺著憨攒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阀参。 梳的紋絲不亂的頭發(fā)上肝集,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音蛛壳,去河邊找鬼杏瞻。 笑死,一個胖子當著我的面吹牛衙荐,可吹牛的內(nèi)容都是我干的捞挥。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼忧吟,長吁一口氣:“原來是場噩夢啊……” “哼砌函!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤讹俊,失蹤者是張志新(化名)和其女友劉穎垦沉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仍劈,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡厕倍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贩疙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讹弯。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖这溅,靈堂內(nèi)的尸體忽然破棺而出组民,到底是詐尸還是另有隱情,我是刑警寧澤芍躏,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布邪乍,位于F島的核電站,受9級特大地震影響对竣,放射性物質(zhì)發(fā)生泄漏庇楞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一否纬、第九天 我趴在偏房一處隱蔽的房頂上張望吕晌。 院中可真熱鬧,春花似錦临燃、人聲如沸睛驳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乏沸。三九已至,卻和暖如春爪瓜,著一層夾襖步出監(jiān)牢的瞬間蹬跃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工铆铆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蝶缀,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓薄货,卻偏偏與公主長得像翁都,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谅猾,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍閱讀 7,333評論 3 87
  • Java多線程學習 [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,949評論 1 18
  • 寫在前面的話: 這篇博客是我從這里“轉(zhuǎn)載”的柄慰,為什么轉(zhuǎn)載兩個字加“”呢鳍悠?因為這絕不是簡單的復制粘貼,我花了五六個小...
    SmartSean閱讀 4,711評論 12 45
  • 1. Java基礎部分 基礎部分的順序:基本語法先煎,類相關的語法贼涩,內(nèi)部類的語法,繼承相關的語法薯蝎,異常的語法,線程的語...
    子非魚_t_閱讀 31,581評論 18 399
  • 本文主要講了java中多線程的使用方法谤绳、線程同步占锯、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應的一些線程函數(shù)用法缩筛、概述等消略。 首先講...
    李欣陽閱讀 2,439評論 1 15