多線程安全問題兩種處理方法 synchronized 和 runnable

package com.atguigu.java;

/**
 * 例子:創建三個窗口賣票,總票數爲100張.使用實現Runnable接口的方式
 *
 * 1.問題:賣票過程中,出現了重票、錯票 -->出現了線程的安全問題
 * 2.問題出現的原因:當某個線程操作車票的過程中,尚未操作完成時,其他線程參與進來,也操作車票。
 * 3.如何解決:當一個線程a在操作ticket的時候,其他線程不能參與進來。直到線程a操作完ticket時,其他
 *            線程纔可以開始操作ticket。這種情況即使線程a出現了阻塞,也不能被改變。
 *
 *
 * 4.在Java中,我們通過同步機制,來解決線程的安全問題。
 *
 *  方式一:同步代碼塊
 *
 *   synchronized(同步監視器){
 *      //需要被同步的代碼
 *
 *   }
 *  說明:1.操作共享數據的代碼,即爲需要被同步的代碼。  -->不能包含代碼多了,也不能包含代碼少了。
 *       2.共享數據:多個線程共同操作的變量。比如:ticket就是共享數據。
 *       3.同步監視器,俗稱:鎖。任何一個類的對象,都可以充當鎖。
 *          要求:多個線程必須要共用同一把鎖。
 *
 *       補充:在實現Runnable接口創建多線程的方式中,我們可以考慮使用this充當同步監視器。
 *  方式二:同步方法。
 *     如果操作共享數據的代碼完整的聲明在一個方法中,我們不妨將此方法聲明同步的。
 *
 *
 *  5.同步的方式,解決了線程的安全問題。---好處
 *    操作同步代碼時,只能有一個線程參與,其他線程等待。相當於是一個單線程的過程,效率低。 ---侷限性
 *
 * @author shkstart
 * @create 2019-02-13 下午 4:47
 */
class Window1 implements Runnable{

    private int ticket = 100;
//    Object obj = new Object();
//    Dog dog = new Dog();
    @Override
    public void run() {
//        Object obj = new Object();
        while(true){
            synchronized (this){//此時的this:唯一的Window1的對象   //方式二:synchronized (dog) {

                if (ticket > 0) {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);


                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}


public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}


class Dog{

}
package com.atguigu.java;

/**
 * @author shkstart
 * @create 2019-02-15 上午 11:15
 */
/**
 * 使用同步代碼塊解決繼承Thread類的方式的線程安全問題
 *
 * 例子:創建三個窗口賣票,總票數爲100張.使用繼承Thread類的方式
 *
 * 說明:在繼承Thread類創建多線程的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器。
 *
 * @author shkstart
 * @create 2019-02-13 下午 4:20
 */
class Window2 extends Thread{


    private static int ticket = 100;

    private static Object obj = new Object();

    @Override
    public void run() {

        while(true){
            //正確的
//            synchronized (obj){
            synchronized (Window2.class){//Class clazz = Window2.class,Window2.class只會加載一次
                //錯誤的方式:this代表着t1,t2,t3三個對象
//              synchronized (this){

                if(ticket > 0){

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(getName() + ":賣票,票號爲:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }

        }

    }
}


public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

package com.atguigu.java;

/**
 * 使用同步方法解決實現Runnable接口的線程安全問題
 *
 *
 *  關於同步方法的總結:
 *  1. 同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。
 *  2. 非靜態的同步方法,同步監視器是:this
 *     靜態的同步方法,同步監視器是:當前類本身
 *
 * @author shkstart
 * @create 2019-02-15 上午 11:35
 */


class Window3 implements Runnable {

    private int ticket = 100;

    @Override
    public void run() {
        while (true) {

            show();
        }
    }

    private synchronized void show(){//同步監視器:this
        //synchronized (this){

            if (ticket > 0) {

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);

                ticket--;
            }
        //}
    }
}


public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w = new Window3();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}

package com.atguigu.java;

/**
 * 使用同步方法處理繼承Thread類的方式中的線程安全問題
 *
 * @author shkstart
 * @create 2019-02-15 上午 11:43
 */
class Window4 extends Thread {


    private static int ticket = 100;

    @Override
    public void run() {

        while (true) {

            show();
        }

    }
    private static synchronized void show(){//同步監視器:Window4.class
        //private synchronized void show(){ //同步監視器:t1,t2,t3。此種解決方式是錯誤的
        if (ticket > 0) {

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);
            ticket--;
        }
    }
}


public class WindowTest4 {
    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章