1. 程式人生 > >18、多線程 (線程安全、線程同步、等待喚醒機制、單例設計模式)

18、多線程 (線程安全、線程同步、等待喚醒機制、單例設計模式)

正在執行 喚醒 數據資源 線程等待 rgs 上一個 註意 current ring

線程操作共享數據的安全問題

*A:線程操作共享數據的安全問題
    如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。
    程序每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

售票的案例

*A:售票的案例
    /*
        - 多線程並發訪問同一個數據資源
        - 3個線程,對一個票資源,出售
    */
    public class ThreadDemo {
        public static void main(String[] args) {
            //創建Runnable接口實現類對象
            Tickets t = new Tickets();
            //創建3個Thread類對象,傳遞Runnable接口實現類
            Thread t0 = new Thread(t);
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            t0.start();
            t1.start();
            t2.start();
        }
    }
    
    public class Tickets implements Runnable {
        //定義出售的票源
        private int ticket = 100;
        private Object obj = new Object();
        
        public void run() {
            while (true) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + " 出售第 " + ticket--);
                }
    
            }
        }
    }

線程安全問題引發?

*A:線程安全問題引發
    /*
     * 多線程並發訪問同一個數據資源
     * 3個線程,對一個票資源,出售
     */
    public class ThreadDemo {
        public static void main(String[] args) {
            //創建Runnable接口實現類對象
            Tickets t = new Tickets();
            //創建3個Thread類對象,傳遞Runnable接口實現類
            Thread t0 = new Thread(t);
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            t0.start();
            t1.start();
            t2.start();

        }
    }
    /*
     *  通過線程休眠,出現安全問題
     */
    public class Tickets implements Runnable{

        //定義出售的票源
        private int ticket = 100;
        private Object obj = new Object();

        public void run(){
            while(true){

                //對票數判斷,大於0,可以出售,變量--操作
                if( ticket > 0){
                    try{
                        Thread.sleep(10); //加了休眠讓其他線程有執行機會
                    }catch(Exception ex){}
                    System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                }
            }
        }
    }

同步代碼塊解決線程安全問題

*A:同步代碼塊解決線程安全問題
    *A:售票的案例
    /*
     * 多線程並發訪問同一個數據資源
     * 3個線程,對一個票資源,出售
     */
    public class ThreadDemo {
        public static void main(String[] args) {
            //創建Runnable接口實現類對象
            Tickets t = new Tickets();
            //創建3個Thread類對象,傳遞Runnable接口實現類
            Thread t0 = new Thread(t);
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            t0.start();
            t1.start();
            t2.start();
    
        }
    }
    /*
     *  通過線程休眠,出現安全問題
     *  解決安全問題,Java程序,提供技術,同步技術
     *  公式:
     *    synchronized(任意對象){
     *      線程要操作的共享數據
     *    }
     *    同步代碼塊
     */
    public class Tickets implements Runnable{
    
        //定義出售的票源
        private int ticket = 100;
        private Object obj = new Object();
    
        public void run(){
            while(true){
                //線程共享數據,保證安全,加入同步代碼塊
                synchronized(obj){
                    //對票數判斷,大於0,可以出售,變量--操作
                    if( ticket > 0){
                        try{
                            Thread.sleep(10);
                        }catch(Exception ex){}
                        System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                    }
                }
            }
        }
    }

同步代碼塊的執行原理

*A:同步代碼塊的執行原理
    同步代碼塊: 在代碼塊聲明上 加上synchronized
    synchronized (鎖對象) {
        可能會產生線程安全問題的代碼
    }
    同步代碼塊中的鎖對象可以是任意的對象;但多個線程時,要使用同一個鎖對象才能夠保證線程安全。

同步的上廁所原理

*A:同步的上廁所原理
    a:不使用同步:線程在執行的過程中會被打擾
        線程比喻成人
        線程執行代碼就是上一個廁所
        第一個人正在上廁所,上到一半,被另外一個人拉出來
    b:使用同步:
        線程比喻成人
        線程執行代碼就是上一個廁所
        鎖比喻成廁所門
        第一個人上廁所,會鎖門
        第二個人上廁所,看到門鎖上了,等待第一個人上完再去上廁所

同步方法

*A:同步方法:
    /*
    - 多線程並發訪問同一個數據資源
    - 3個線程,對一個票資源,出售
    */
    public class ThreadDemo {
        public static void main(String[] args) {
            //創建Runnable接口實現類對象
            Tickets t = new Tickets();
            //創建3個Thread類對象,傳遞Runnable接口實現類
            Thread t0 = new Thread(t);
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);

            t0.start();
            t1.start();
            t2.start();

        }
    }
    
*B:同步方法
    /*
     *  采用同步方法形式,解決線程的安全問題
     *  好處: 代碼簡潔
     *  將線程共享數據,和同步,抽取到一個方法中
     *  在方法的聲明上,加入同步關鍵字
     *
     *  問題:
     *    同步方法有鎖嗎,肯定有,同步方法中的對象鎖,是本類對象引用 this
     *    如果方法是靜態的呢,同步有鎖嗎,絕對不是this
     *    鎖是本類自己.class 屬性
     *    靜態方法,同步鎖,是本類類名.class屬性
     */
    public class Tickets implements Runnable{
        //定義出售的票源
        private  int ticket = 100;

        public void run(){
            while(true){
                payTicket();
            }
        }

        public  synchronized void payTicket(){
            if( ticket > 0){
                try{
                    Thread.sleep(10);
                }catch(Exception ex){}
                System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
            }

        }
    }

JDK1.5新特性Lock接口

*A:JDK1.5新特性Lock接口
    查閱API,查閱Lock接口描述,Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。
        Lock接口中的常用方法
            void lock()
            void unlock()
    Lock提供了一個更加面對對象的鎖,在該鎖中提供了更多的操作鎖的功能。
    我們使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,對電影院賣票案例中Ticket

Lock接口改進售票案例

*A:Lock接口改進售票案例
    /*
     * 多線程並發訪問同一個數據資源
     * 3個線程,對一個票資源,出售
     */
    public class ThreadDemo {
        public static void main(String[] args) {
            //創建Runnable接口實現類對象
            Tickets t = new Tickets();
            //創建3個Thread類對象,傳遞Runnable接口實現類
            Thread t0 = new Thread(t);
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            t0.start();
            t1.start();
            t2.start();

        }
    }
    /*
     *  使用JDK1.5 的接口Lock,替換同步代碼塊,實現線程的安全性
     *  Lock接口方法:
     *     lock() 獲取鎖
     *     unlock()釋放鎖
     *  實現類ReentrantLock
     */
    public class Tickets implements Runnable{

        //定義出售的票源
        private int ticket = 100;
        //在類的成員位置,創建Lock接口的實現類對象
        private Lock lock = new ReentrantLock();

        public void run(){
            while(true){
                //調用Lock接口方法lock獲取鎖
                lock.lock();
                //對票數判斷,大於0,可以出售,變量--操作
                if( ticket > 0){
                    try{
                        Thread.sleep(10);
                        System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                    }catch(Exception ex){

                    }finally{
                        //釋放鎖,調用Lock接口方法unlock
                        lock.unlock();
                    }
                }
            }
        }
    }

線程的死鎖原理?

*A:線程的死鎖原理  
    當線程任務中出現了多個同步(多個鎖)  時,如果同步中嵌套了其他的同步。這時容易引發一種現象:程序出現無限等待,這種現象我們稱為死鎖。這種情況能避免就避免掉。
    synchronzied(A鎖){
        synchronized(B鎖){
        }
    }

線程的死鎖代碼實現

*A:線程的死鎖代碼實現
    public class DeadLock implements Runnable{
        private int i = 0;
        public void run(){
            while(true){
                if(i%2==0){
                    //先進入A同步,再進入B同步
                    synchronized(LockA.locka){
                        System.out.println("if...locka");
                        synchronized(LockB.lockb){
                            System.out.println("if...lockb");
                        }
                    }
                }else{
                    //先進入B同步,再進入A同步
                    synchronized(LockB.lockb){
                        System.out.println("else...lockb");
                        synchronized(LockA.locka){
                            System.out.println("else...locka");
                        }
                    }
                }
                i++;
            }
        }
    }
    public class DeadLockDemo {
        public static void main(String[] args) {
            DeadLock dead = new DeadLock();
            Thread t0 = new Thread(dead);
            Thread t1 = new Thread(dead);
            t0.start();
            t1.start();
        }
    }
    public class LockA {
        private LockA(){}
    
        public  static final LockA locka = new LockA();
    }
    public class LockB {
        private LockB(){}
    
        public static final LockB lockb = new LockB();
    }

線程等待與喚醒案例介紹

*A:線程等待與喚醒案例介紹 
    等待喚醒機制所涉及到的方法:
        wait() :等待,將正在執行的線程釋放其執行資格 和 執行權,並存儲到線程池中。
        notify():喚醒,喚醒線程池中被wait()的線程,一次喚醒一個,而且是任意的。
        notifyAll(): 喚醒全部:可以將線程池中的所有wait() 線程都喚醒。
    其實,所謂喚醒的意思就是讓 線程池中的線程具備執行資格。必須註意的是,這些方法都是在 同步中才有效。同時這些方法在使用時必須標明所屬鎖,這樣才可以明確出這些方法操作的到底是哪個鎖上的線程。

線程等待與喚醒案例資源類編寫

*A:線程等待與喚醒案例資源類編寫
    /*
     *  定義資源類,有2個成員變量
     *  name,sex
     *  同時有2個線程,對資源中的變量操作
     *  1個對name,age賦值
     *  2個對name,age做變量的輸出打印
     */
    public class Resource {
        public String name;
        public String sex;
    }

線程等待與喚醒案例輸入和輸出線程

*A:線程等待與喚醒案例輸入和輸出線程
    /*
      *  輸入的線程,對資源對象Resource中成員變量賦值
      *  一次賦值 張三,男
      *  下一次賦值 lisi,nv
    */
    public class Input implements Runnable {
        private Resource r=new Resource();
        public void run() {
            int i=0;
            while(true){
                if(i%2==0){
                    r.name="張三";
                    r.sex="男";
                }else{
                    r.name="lisi";
                    r.sex="女";
                }
                i++;
            }
        }
    }
    
    /*
     *  輸出線程,對資源對象Resource中成員變量,輸出值
     */
    public class Output implements Runnable {
        private Resource r=new Resource() ;
    
        public void run() {
            while(true){
                System.out.println(r.name+"..."+r.sex);
            }
        }
    }

線程等待與喚醒案例測試類

*A:線程等待與喚醒案例測試類
    /*
     *  開啟輸入線程和輸出線程,實現賦值和打印值
     */
    public class ThreadDemo{
        public static void main(String[] args) {
            Resource r = new Resource();
    
            Input in = new Input();
            Output out = new Output();
    
            Thread tin = new Thread(in);
            Thread tout = new Thread(out);
    
            tin.start();
            tout.start();
        }
    }

線程等待與喚醒案例null值解決

*A:線程等待與喚醒案例null值解決
    /*
    *  輸入的線程,對資源對象Resource中成員變量賦值
    *  一次賦值 張三,男
    *  下一次賦值 lisi,nv
    */
    public class Input implements Runnable {
        private Resource r;
        public Input(Resource r){
            this.r=r;
        }
        public void run() {
            int i=0;
            while(true){
                if(i%2==0){
                    r.name="張三";
                    r.sex="男";
                }else{
                    r.name="lisi";
                    r.sex="女";
                }
                i++;
            }
        }
    }
    
    /*
     *  輸出線程,對資源對象Resource中成員變量,輸出值
     */
    public class Output implements Runnable {
        private Resource r;
        public Output(Resource r){
            this.r=r;
        }
        public void run() {
            while(true){
                System.out.println(r.name+"..."+r.sex);
            }
        }
    }
    
       }
    /*
     *  開啟輸入線程和輸出線程,實現賦值和打印值
     */
    public class ThreadDemo{
        public static void main(String[] args) {
    
            Resource r = new Resource();
    
            Input in = new Input(r);
            Output out = new Output(r);
    
            Thread tin = new Thread(in);
            Thread tout = new Thread(out);
    
            tin.start();
            tout.start();
        }
    }

線程等待與喚醒案例數據安全解決

*A:線程等待與喚醒案例數據安全解決
    /*
      *  輸入的線程,對資源對象Resource中成員變量賦值
      *  一次賦值 張三,男
      *  下一次賦值 lisi,nv
    */
    public class Input implements Runnable {
        private Resource r;
        public Input(Resource r){
            this.r=r;
        }

        public void run() {
            int i=0;
            while(true){
                synchronized(r){
                    if(i%2==0){
                        r.name="張三";
                        r.sex="男";
                    }else{
                        r.name="lisi"
                        r.sex="女"
                    }
                    i++;
                }

            }
        }

        /*
         *  輸出線程,對資源對象Resource中成員變量,輸出值
         */
        public class Output implements Runnable {
            private Resource r;
            public Output(Resource r){
                this.r=r;
            }
            public void run() {
                while(true){
                    synchronized(r){
                        System.out.println(r.name+"..."+r.sex);
                    }
                }
            }
        }

    }
    /*
     *  開啟輸入線程和輸出線程,實現賦值和打印值
     */
    public class ThreadDemo{
        public static void main(String[] args) {

            Resource r = new Resource();

            Input in = new Input(r);
            Output out = new Output(r);

            Thread tin = new Thread(in);
            Thread tout = new Thread(out);

            tin.start();
            tout.start();
        }
    }

線程等待與喚醒案例通信的分析

*A:線程等待與喚醒案例通信的分析
    輸入:賦值後,執行方法wait()永遠等待
    輸出:變量值打印輸出,在輸出等待之前,喚醒
    輸入的notify(),自己在wait()永遠等待
    輸入:被喚醒後,重新對變量賦值,賦值後,必須喚醒輸出的線程notify(),自己的wait()

線程等待與喚醒案例的實現

*A 線程等待與喚醒案例的實現
    /*
     *  定義資源類,有2個成員變量
     *  name,sex
     *  同時有2個線程,對資源中的變量操作
     *  1個對name,age賦值
     *  2個對name,age做變量的輸出打印
     */
    public class Resource {
        public String name;
        public String sex;
        public boolean flag = false;
    }

    /*
     *  輸入的線程,對資源對象Resource中成員變量賦值
     *  一次賦值 張三,男
     *  下一次賦值 lisi,nv
     */
    public class Input implements Runnable {
        private Resource r ;

        public Input(Resource r){
            this.r = r;
        }

        public void run() {
            int i = 0 ;
            while(true){
                synchronized(r){
                    //標記是true,等待
                    if(r.flag){
                        try{r.wait();}catch(Exception ex){}
                    }

                    if(i%2==0){
                        r.name = "張三";
                        r.sex = "男";
                    }else{
                        r.name = "lisi";
                        r.sex = "nv";
                    }
                    //將對方線程喚醒,標記改為true
                    r.flag = true;
                    r.notify();
                }
                i++;
            }
        }

    }

    /*
     *  輸出線程,對資源對象Resource中成員變量,輸出值
     */
    public class Output implements Runnable {
        private Resource r ;

        public Output(Resource r){
            this.r = r;
        }
        public void run() {
            while(true){
                synchronized(r){
                    //判斷標記,是false,等待
                    if(!r.flag){
                        try{r.wait();}catch(Exception ex){}
                    }
                    System.out.println(r.name+".."+r.sex);
                    //標記改成false,喚醒對方線程
                    r.flag = false;
                    r.notify();
                }
            }
        }

    }

    /*
     *  開啟輸入線程和輸出線程,實現賦值和打印值
     */
    public class ThreadDemo{
        public static void main(String[] args) {

            Resource r = new Resource();
    
            Input in = new Input(r);
            Output out = new Output(r);

            Thread tin = new Thread(in);
            Thread tout = new Thread(out);

            tin.start();
            tout.start();
        }
    }

總結

18、多線程 (線程安全、線程同步、等待喚醒機制、單例設計模式)