多線程

線程基礎

  1. 線程的五種狀態

    1. 創建(New):使用new創建一個線程時的狀態,尚未啓動。
    2. 就緒(Runnable):調用start()啓動線程,加入就緒隊列,等待被調度進CPU運行
    3. 運行(Run):處於就緒狀態的線程獲取了cpu的執行權
    4. 阻塞(Block):由於某些原因該線程放棄了cpu的使用權。停止執行。除非線程進入可運行的狀態,纔會有機會獲取cpu的使用權。
      • 等待阻塞:運行中的線程執行wait方法,這時候該線程會被放入等待隊列。
      • 同步阻塞:運行中的線程獲取同步鎖,如果該同步鎖被別的線程佔用,這個線程會成被放入鎖池,等待其他線程釋放同步鎖。
      • 其他阻塞:運行的線程執行sleep或者join方法這個線程會成爲阻塞狀態。當sleep超時,join等待線程終止,該線程會進入可運行狀態。
    5. 死亡(Dead):線程run mian 執行完畢後,或者因爲某些異常產生退出了 run 方法,該線程的生命週期結束。
  2. 線程狀態轉換圖
    狀態轉換

  3. 線程有關方法

    1. sleep(time):使線程進入阻塞狀態,但不釋放鎖,睡眠時間發到後自動進入就緒狀態。
      作用:給其他線程執行機會。
    2. wait():使線程進入阻塞狀態,釋放鎖,等到被其他線程notify()時進入就緒狀態。
    3. interrupt():停止線程,調用時拋出中斷異常。
    4. setDaemon(true):後臺線程,當所有的前臺線程都結束了,後臺線程就結束。
      注意:在啓動前調用。作用:爲前臺線程提供服務。
    5. setPriority(Thead.MAX_PRIORITY):設置線程的優先級(1-10),默認優先級爲5。
    6. join():當A線程執行到B線程的join()時,A就會等待,直到B結束A纔會執行。
      作用:臨時加入線程,使該線程運行完,其他線程才繼續。
    7. yield():使當前線程放棄分得的CPU時間片,進入就緒狀態。
      作用:使同優先級或更高優先級的線程有執行機會。

繼承Thread類

  1. 代碼演示

    class Test extends Thread 
    {
        Test(String name)
        {
            super(name);
        }
        //run()存儲線程要運行的代碼
        public void run()
        {
            for(int x = 0; x < 60;x++)
            {
                System.out.println(Thread.currentThread().getName()+"   run....."+x);
            }
        }
    }
    
    class  ThreadDemo1
    {
        public static void main(String[] args) 
        {
            Test t1 = new Test("one");
            Test t2 = new Test("two");
            t1.start();
            t2.start();
        }
    }
    
  2. 存在的不足

    1. 子類無法多繼承。
    2. 不適合資源共享。

實現Runnable接口

  1. 代碼演示

    class Ticket implements Runnable
    {
        private int tick = 100;
        public void run()
        {
            while(true)
            {
                if(tick>0)
                {
                    System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
                }
            }
        }
    }
    class  ThreadDemo3
    {
        public static void main(String[] args) 
        {
            Ticket t  = new Ticket();
    
            //創建線程的同時明確線程要運行的代碼
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            Thread t3 = new Thread(t);
            Thread t4 = new Thread(t);
    
            //啓動線程,使線程處於就緒狀態,等待被調度
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
  2. 注意:實現Runnable接口的類不是線程類。創建線程對象的是Thread或Thread的子類。

多線程的安全問題

  1. 代碼演示安全問題

    class Ticket implements Runnable
    {
        //線程的共享數據
        private int tick = 100;
        public void run()
        {
            while(true)
            {
                if(tick>0)
                {
                    //爲了演示可能出現的問題,讓當前線程釋放執行權
                    try{Thread.sleep(10);} catch(Exception e){}
                    System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
                }
            }
        }
    }

    當多線程用到共享資源時,由於CPU的時間片特性,不管run()有沒有執行完畢,CPU時間片一旦執行完一個線程的時間片,會自動跳轉到另一個線程的run()中去執行其時間片,這樣就可能導致共享數據的不一致,也即出現了多線程安全問題。

  2. 解決方案

    class Ticket implements Runnable
    {
        private int tick = 100;
        public void run()
        {
            while(true)
            {
                //在涉及到共享數據的代碼上加互斥鎖
                synchronized(this)
                {
                    if(tick>0)
                    {
                        try{Thread.sleep(10);} catch(Exception e){}
                        System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
                    }
                }
            }
        }
    }

互斥鎖

  1. 互斥鎖的理解

    互斥鎖相當於公共廁所的一把鎖,公共廁所是一個共享資源,爲了使當前正在使用資源的人不被其他人打擾,在門上加一把鎖,裏面的人不開門(釋放鎖),外面的人就進不去。

  2. 如何判斷哪些代碼需要加鎖

    1. 明確哪些代碼是多線程運行代碼
    2. 明確共享數據
    3. 明確多線程運行代碼中哪些語句是操作共享數據的。
  3. 加鎖後同步的前提

    1. 有兩個或兩個以上的線程
    2. 多個線程使用同一個鎖
  4. 加鎖的弊端

    多個線程需要判斷鎖,較爲消耗資源。

  5. 鎖對象

    1. 同步代碼塊的鎖可以是任意對象。
    2. 同步函數的鎖是this。
    3. 靜態同步函數的鎖是Class對象。

多線程間的通信

  1. 問題描述

    當多個線程在操作同一個資源,但是操作的動作不同(run()不同),就涉及到了線程間的通信問題。

  2. 解決方法:用標記+等待喚醒機制wait()和notify()實現同步

    1. wait():使正在執行的線程等待,進入阻塞狀態。
    2. notify():喚醒線程池中等待的線程,進入就緒狀態。
    3. notifyAll() :喚醒線程池中所有線程。

    注意:這些方法都在互斥鎖中使用,因爲它們都被互斥鎖對象調用。 以下是代碼示例。

    class Res
    {
        private String name;
        private String sex;
        private boolean flag = false;   //是否釋放鎖的標記
    
        //送入
        public synchronized void  set(String name,String sex)   //加鎖
        {
            if(flag)        //有煤了,等,釋放鎖
                try{this.wait();} catch(Exception e){}  
    
            this.name = name;
            this.sex = sex;
    
            flag = true;        //標記有資源了
            this.notify();      //喚醒對方線程,叫對方送走資源
        }
    
        //送走
        public synchronized void out()
        {
            if(!flag)       //沒有資源,等
                try{this.wait();} catch(Exception e){}  
    
            System.out.println(name+"........"+sex);
    
            flag = false;   //標記沒有資源了
            this.notify();  //喚醒對方線程,叫對方送資源
        }
    }
    
    class Input implements Runnable
    {
        Res r;
        Input(Res r)
        {
            this.r = r;
        }
        public void run()
        {
            int x = 0;
            while(true)
            {
                if(x == 0)  
                    r.set("mike","male");
                else
                    r.set("lili","女女女女女女");
                x = (x+1) % 2;
            }
        }
    }
    
    class Output implements Runnable
    {
        Res r;
        Output(Res r)
        {
            this.r = r;
        }
        public void run()
        {
            while(true)
            {
                r.out();
            }
        }
    }
    
    class  ThreadDemo7
    {
        public static void main(String[] args) 
        {
            //要操作的共享資源
            Res r = new Res();
            //開啓兩個線程同時運行
            new Thread(new Input(r)).start();
            new Thread(new Output(r)).start();
        }
    }
  3. 需要同步的幾種情況

    1. 兩個操作一樣的線程:直接加鎖(鎖對象是本身對象)
    2. 兩個操作不一樣的線程:加鎖+判斷標記+等待喚醒機制(notify())
    3. 兩個執行A操作的線程、兩個執行B操作的線程:加鎖+循環判斷標記+等待喚醒機制(notifyAll())
  4. 生產者消費者問題代碼示例

    //共享資源
    class Resource
    {
        private String name;            //資源名稱
        private int count = 1;          //操作資源的次數
        private boolean flag = false;       //是否釋放鎖的標記
    
        public synchronized void set(String name)       //隱式的鎖機制
        {
            while(flag)                 //有資源
                try{this.wait();} catch(Exception e){}  //等待的同時釋放鎖
    
            this.name = name + "--" + count++;
            System.out.println(Thread.currentThread().getName()+"....生產者...."+this.name);
    
            flag = true;                    //有資源了
            this.notifyAll();               //喚醒所有等待隊列中的線程
        }
        public synchronized void out()
        {
            while(!flag)                    //沒有資源
                try{this.wait();} catch(Exception e){}
    
            System.out.println(Thread.currentThread().getName()+"....消費者.........."+this.name);
    
            flag = false;                   //資源被取走了
            this.notifyAll();               //喚醒所有等待隊列中的線程
        }
    }
    
    //生產者
    class Producer implements Runnable 
    {
        private Resource res;
        Producer(Resource res)
        {
            this.res = res;
        }
        public void run()
        {
            while(true)
            {
                res.set("+商品+");
            }
        }
    }
    
    //消費者
    class Consumer implements Runnable
    {
        private Resource res;
        Consumer(Resource res)
        {
            this.res = res;
        }
        public void run()
        {
            while(true)
            {
                res.out();
            }
        }
    }
    
    class ThreadDemo8
    {
        public static void main(String[] args) 
        {
            //共享資源對象
            Resource r = new Resource();
    
            //生產者、消費者對象操作同一個資源
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
    
            //兩個生產者線程、兩個消費者線程
            Thread t1 = new Thread(pro);
            Thread t2 = new Thread(pro);
            Thread t3 = new Thread(con);
            Thread t4 = new Thread(con);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

JDK5.0升級後的同步解決方案

  1. 升級內容

    1. 將同步synchronized替換成Lock接口
    2. 由於wait()、notify()都要標識自己所屬的鎖,因此將Object中的wait()、notify()、 notifyAll()封裝成Condition監視器對象,該對象可以通過Lock鎖對象獲取,且一個鎖可以對應多個監視器對象。
  2. 升級後的好處

    1. 顯式的鎖機制,使加鎖和釋放鎖操作更透明。
    2. 顯式的等待喚醒機制,可以有多個Condition監視器對象,使不會喚醒本方。
    3. 由於一個鎖可以對應多個Condition監視器對象,因此不容易發生死鎖。
  3. 生產者消費者問題代碼示例

    import java.util.concurrent.locks.*;
    
    //要操作的共享資源
    class Resource
    {
        private String name;                    //資源名稱
        private int count = 1;                  //操作資源的次數
        private boolean flag = false;               //是否釋放鎖的標記
        private Lock lock = new ReentrantLock();        //接口引用指向子類對象
        private Condition condition_pro = lock.newCondition();  //生產者監視器
        private Condition condition_con = lock.newCondition();  //消費者監視器
    
        //設置資源
        public void set(String name) throws InterruptedException
        {
            lock.lock();        //加鎖
            try
            {
                while(flag)
                    condition_pro.await();  //生產者等待,沒有釋放鎖
    
                this.name = name + "--" + count++;
                System.out.println(Thread.currentThread().getName()+"....生產者...."+this.name);
    
                flag = true;
                condition_con.signal();     //消費者醒來
            }
    
            finally         //在await()之前必須先釋放鎖
            {
                lock.unlock();  //釋放鎖
            }
        }
        //獲取資源
        public void out() throws InterruptedException
        {
            lock.lock();        //加鎖
            try
            {
                while(!flag)
                    condition_con.await();  //消費者等待,沒有釋放鎖
    
                System.out.println(Thread.currentThread().getName()+"....消費者.........."+this.name);
    
                flag = false;
                condition_pro.signal();     //生產者醒來
            }
            finally         //在await()之前必須先釋放鎖
            {
                lock.unlock();
            }
        }
    }
    
    //生產者
    class Producer implements Runnable 
    {
        private Resource res;
        Producer(Resource res)
        {
            this.res = res;
        }
        public void run()
        {
            while(true)
            {
                try
                {
                    res.set("+商品+");
                }
                catch (InterruptedException e)
                {
                }
            }
        }
    }
    
    //消費者
    class Consumer implements Runnable
    {
        private Resource res;
        Consumer(Resource res)
        {
            this.res = res;
        }
        public void run()
        {
            while(true)
            {
                try
                {
                    res.out();
                }
                catch (InterruptedException e)
                {
                }
            }
        }
    }
    class ThreadDemo9
    {
        public static void main(String[] args) 
        {
            //創建共享資源對象
            Resource r = new Resource();
    
            //創建生產者消費者對象,操作同一個資源對象
            Producer pro = new Producer(r);
            Consumer con = new Consumer(r);
    
            //兩個生產者線程,兩個消費者線程
            Thread t1 = new Thread(pro);
            Thread t2 = new Thread(pro);
            Thread t3 = new Thread(con);
            Thread t4 = new Thread(con);
    
            //開啓線程對象
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章