JAVA | 線程(四)線程通信

一、傳統的線程通信

  • 要藉助於Object類提供的wait()、notift()、notifyAll(),這三個方法都要由同步監視器來調用,分下面兩種情況:

    • synchronized 修飾的同步方法,同步監視器就是該類的默認實例(this),所以可以直接在同步代碼塊中調用這三個方法
    • synchronized 修飾的同步代碼塊,同步監視器是synchronized 括號中的對象,所以要用對象調用這三個方法。
  • wait():導致線程等待,直到其他線程調用該**同步監視器的notify()/motifyAll()方法

  • notify:喚醒此同步監視器上的單個線程,如果所有線程都在此同步監視器下等待,則隨機喚醒一個,只有當前線程放棄對該同步監視器的鎖定後(調用wait()),纔可以執行被喚醒的線程

  • notifyAll():喚醒此同步監聽器上的所有等待的線程,只有當前線程放棄對該同步監視器的鎖定後(調用wait()),纔可以執行被喚醒的線程

public class Account {
    // 銀行賬戶
    private String accountNo;
    //餘額
    private int balance;
    // 是否有錢
    private boolean flag = false;
    
    public Account(String accountNo, int balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }
    
    /**
     * 取錢
     */
   public synchronized void draw(int monny) throws InterruptedException {
       if (!flag){
           // 如果沒有錢可取,就等待
           wait();
       }else {
           if (balance>=monny){
               Log.e("testthread",Thread.currentThread().getName()+"取錢成功:"+monny);

               balance-=monny;
               Log.e("testthread","餘額:"+balance);
               flag = false;

               // 喚醒等待的線程
               notifyAll();
           }else {
               Log.e("testthread",Thread.currentThread().getName()+"取錢失敗,餘額不足:"+balance);
           }
       }
   }

    /**
     * 存錢
     */
   public synchronized void deposit(int monny) throws InterruptedException {
       if (flag){
           //有錢就等着
           wait();
       }else {
           Log.e("testthread",Thread.currentThread().getName()+"存錢成功:"+monny);
           balance +=monny;
           Log.e("testthread","餘額:"+balance);
           flag = true;
           // 喚醒等待的線程
           notifyAll();
       }
   }
}
// 取錢的線程
public class DrawThread extends Thread {
    // 取錢的賬戶
    private Account account;

    // 取錢的金額
    private int monny;

    DrawThread(String name,Account account,int monny){
        super(name);
        this.account = account;
        this.monny = monny;
    }

    @Override
    public void run() {
        super.run();
        for (int i=0;i<100;i++){
           account.draw(monny);
        }
    }
}
//存錢的線程
public class DepositThread extends Thread {
    private Account account;
    private int monny;

    DepositThread(String name,Account account,int monny){
        super(name);
        this.account = account;
        this.monny = monny;
    }

    @Override
    public void run() {
        super.run();
        for (int i=0;i<100;i++){
             account.deposit(monny);
        }
    }
}

mBntFun5.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Account account = new Account("admin",0);
               new DrawThread("取錢A",account,800).start();
               new DepositThread("存錢A",account,800).start();
               new DepositThread("存錢B",account,800).start();
               new DepositThread("存錢C",account,800).start();
            }
        });

在這裏插入圖片描述

二、使用Condition控制線程通信

  • 當使用Lock對象來保證同步的時候,可以使用Condition來保持協調,讓那些持有Lock卻無法繼續執行的線程,釋放Lock對象;也可以喚醒其他處於等待的線程。
  • 此時,Lock代替了同步方法或同步代碼塊的作用,Condition代替了同步監視器的作用
  • Condition被綁在一個Lock對象上
  • 要獲得特點Lock對象的Condition,就要調用Lock.newCondition()
  • Condition提供了三個方法:
    • await():導致線程等到,知道Condition的signal、signalAll來喚醒
    • signal:喚醒此Lock對象上等待的單個線程,如果所有線程都在等待,則隨機喚醒一個,只有當前線程放棄對Lock對象的鎖定(調用await())後,纔可以執行被喚醒的線程
    • signalAll:喚醒此Lock對象上等待的所有線程,只有當前線程放棄對Lock對象的鎖定(調用await())後,纔可以執行被喚醒的線程
public class ConditionAccount {
    private String account;
    private int balance;
    // 是否有錢
    private boolean flag;

    // 顯示定義一個LOCK對象
    private ReentrantLock lock = new ReentrantLock();

    // 定義特定Lock對象的Condition對象
    Condition condition = lock.newCondition();
    
    /**
     * 取錢
     * @param monny
     */
    public void draw(int monny) throws InterruptedException {
        lock.lock();
        try {
            if (!flag){
                //沒有錢,就等着
                condition.await();
            }else {
                if (balance >= monny){
                    Log.e("testthread",Thread.currentThread().getName()+"取錢成功:"+monny);

                    balance -=monny;

                    Log.e("testthread","餘額:"+balance);
                    flag = false;

                    // 喚醒其他線程
                    condition.signalAll();

                }
            }
        }finally {
            lock.unlock();
        }
    }

    /**
     * 存錢
     * @param monny
     */
    public void deposit(int monny) throws InterruptedException {
        lock.lock();
        try {
            if (flag){
                //有錢,就等着
                condition.await();
            }else {
                Log.e("testthread",Thread.currentThread().getName()+"存錢成功:"+monny);

                balance +=monny;

                Log.e("testthread","餘額:"+balance);
                flag = true;

                // 喚醒其他線程
                condition.signalAll();
            }
        }finally {
            lock.unlock();
        }
    }
}

// 存錢的線程
public class DepositThread extends Thread {
    private ConditionAccount account;
    private int monny;

    DepositThread(String name,ConditionAccount account,int monny){
        super(name);
        this.account = account;
        this.monny = monny;
    }

    @Override
    public void run() {
        super.run();
        for (int i=0;i<100;i++){
            try {
                account.deposit(monny);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// 取錢的類
public class DrawThread extends Thread {
    // 取錢的賬戶
    private ConditionAccount account;

    // 取錢的金額
    private int monny;

    DrawThread(String name,ConditionAccount account,int monny){
        super(name);
        this.account = account;
        this.monny = monny;
    }

    @Override
    public void run() {
        super.run();
        for (int i=0;i<100;i++){
            try {
                account.draw(monny);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
mBntFun5.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ConditionAccount account = new ConditionAccount("admin",0);
               new DrawThread("取錢A",account,800).start();
               new DepositThread("存錢A",account,800).start();
               new DepositThread("存錢B",account,800).start();
               new DepositThread("存錢C",account,800).start();
            }
        });

在這裏插入圖片描述

三、使用阻塞隊列(BlockingQueue)控制線程通信

java BlockingQueque的多種實現

  • BlockingQueue這個接口是Queue的子接口,但是它並不是作爲容器,而是用於線程同步的工具
  • 特徵:
  1. 當生產者線程試圖向BlockingQueue中插入元素,但是該隊列已經滿了,則會阻塞
  2. 當消費者線程試圖向BlockingQueue中取出元素,但是該隊列已經空了,則會阻塞

方法

拋出異常 不同返回值 阻塞線程 指定超時時長
隊尾插入元素 add(e) offer(e) put(e) offer(e,time,unit)
隊頭刪除元素 remove() poll() take() poll(time,unit)
獲取,不刪除元素 element() pick()

實現類

  • DelayQueue< E extends Delayed>:
    —>基於 PriorityBlockingQueue 實現的,要求集合元素都實現Delay接口

  • LinkedBlockingQueue< E>:
    –>基於鏈表實現的BlockingQueue隊列

  • ArrayBlockingQueue< E>

  • Synchronous< E>
    –>同步隊列,對該隊列的存取操作必須交替進行

  • PriorityBlockingQueue< E>
    –>並不是標準的阻塞隊列,用remove()、take()、poll()取出元素時,並不是取出隊列中存在時間最長的元素,而是隊列中最小的元素

mBntFun5.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String> (2);
                // 在沒滿時,這裏存入元素,用add()、offer()、Put()都可以
                queue.add("android");
                queue.add("android");

                // 會報錯:java.lang.IllegalStateException: Queue full
                //queue.add("android");

                //會返回false
                //queue.offer("android");

                // 會阻塞線程:程序無響應
                try {
                    queue.put("android");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Log.e("testqueue","阻塞:"+e.getMessage());
                }
            }
        });
// 生產者
    public class Produce extends Thread{
        private BlockingDeque<String> queue;

        Produce(BlockingDeque<String> queue){
            this.queue = queue;
        }

        String[] array = new String[]{
                "AA","BB","CC"
        };

        @Override
        public void run() {
            super.run();
            for (int i=0;i<1000;i++){
                Log.e("testthread",getName()+"準備生產");
                try {
                    Thread.sleep(200);
                    // 存入元素
                    queue.put(array[i%3]);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.e("testthread",getName()+" 生產完成"+queue);
            }
        }
    }

    // 消費者
    public class Consume extends Thread{
        private BlockingDeque<String> queue;
        Consume(BlockingDeque<String> queue){
            this.queue = queue;
        }

        @Override
        public void run() {
            super.run();
            for (int i=0;i<1000;i++){
                Log.e("testthread",getName()+" 準備消費");
                try {
                    Thread.sleep(200);
                    // 存入元素
                    queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.e("testthread",getName()+" 消費完成"+queue);
            }
        }
    }
mBntFun5.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                BlockingDeque<String> queue = new LinkedBlockingDeque<>(1);
                //啓動三個生產現場
                new Produce(queue).start();
                new Produce(queue).start();
                new Produce(queue).start();
                //啓動一個消費線程
                new Consume(queue).start();
            }
        });

在這裏插入圖片描述### 四、線程組和未處理的異常

  • ThreadGroup:線程組,可以對一批線程進行分類管理
  • 用戶創建的所有線程都屬於線程組,如果沒有顯示指定線程組,則屬於默認的線程組,默認情況下和父線程在同一個線程組
  • 一旦線程加入某線程組,知道她死亡,都會一直屬於這個線程組,運行過程組不能更改線程組

設置線程屬於哪一個線程組

  • public Thread(ThreadGroup group, Runnable target)
  • public Thread(ThreadGroup group, String name)
  • public Thread(ThreadGroup group, Runnable target, String name)

ThreadGroup的構造方法:

  • public ThreadGroup(String name)
  • public ThreadGroup(ThreadGroup parent, String name)

ThreadGroup提供的方法:

  • String getName() 獲取線程組的名字
  • boolean isDaemon():是否是後臺線程
  • void setDaemon(boolean daemon):設置爲後臺線程
  • void interrupt():中斷此線程組中的所有線程
  • int activeCount():返回活動線程的數目
  • void setMaxPriority(int pri):設置最高優先級
mBntFun5.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
               ThreadGroup maingroup = Thread.currentThread().getThreadGroup();
               //主線程組:main
                Log.e("testqueue","主線程組:"+maingroup.getName());

                //創建一個新的線程組
                ThreadGroup newgroup = new ThreadGroup("新線程組");
                newgroup.setDaemon(true);
                Log.e("testqueue",maingroup.getName()+":"+newgroup.isDaemon());
            }
        });
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章