線程的基礎知識(五)之線程通信

傳統的線程通信

當線程在系統內運行時,線程的調度具有一定的透明性,程序通常無法準確控制線程的輪換執行,單Java也提供了一些機制來保證線程協調運行。爲了實現這一機制,可以藉助Object類提供的wait(),notify()和notifyAll()三個方法:

  1. wait() :導致當前線程等待,直到其他線程調用該同步監視器的notify()方法或notifyAll()方法來喚醒該線程。調用wait()方法的當前線程會釋放該同步監視器的鎖定;
  2. notify():喚醒在此同步監視器上等待的單個線程,若有多個等待線程,則會隨機喚醒其中一個;
  3. notify() :喚醒在此同步監視器上等待的所有線程。
    演示代碼如下:

Account類:

package com.dalingjia.thead.threadCommunication.tradition;

public class Account {

    //賬號編號
    private String accountNo;
    //賬號餘額
    private Double balance;

    public Account(String accountNo, Double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    public synchronized void draw(double drawAmount) {
        try {
            //賬戶沒錢,等待
            if (!(balance > 0)) {
                wait();
            } else {
                balance -= drawAmount;
                System.out.println(Thread.currentThread().getName() + "<-取走:" + drawAmount + " ,餘額:" + balance);
                notifyAll();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //存錢
    public synchronized void deposit(double depositAmount) {
        try {
            //賬戶有錢,等待
            if (balance > 0) {
                wait();
            } else {
                balance += depositAmount;
                System.out.println(Thread.currentThread().getName() + "->存進:" + depositAmount + " ,餘額:" + balance);
                notifyAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

取錢線程:

package com.dalingjia.thead.threadCommunication.tradition;

public class DrawThread extends Thread {

    private Account account;

    private Double amount;

    public DrawThread(String name, Account account, Double amount) {
        super(name);
        this.account = account;
        this.amount = amount;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            account.draw(amount);
        }
    }
}

存錢線程:

package com.dalingjia.thead.threadCommunication.tradition;

/**
 * 存錢線程
 */
public class DepositThread extends Thread {

    private Account account;

    //存錢數量
    private Double amount;

    public DepositThread(String name, Account account, Double amount) {
        super(name);
        this.account = account;
        this.amount = amount;
    }

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

測試類:

package com.dalingjia.thead.threadCommunication.tradition;

public class Test {

    public static void main(String[] args) {
        //1,定義一個賬戶
        Account account = new Account("thq10000", 0d);
        //2,準備target
        DepositThread depositThread = new DepositThread("存",account, 100d);
        DrawThread drawThread = new DrawThread( "取",account, 100d);
        //3,開啓2個線程
        depositThread.start();
        drawThread.start();
    }
}

使用Condition控制線程通信:

如果程序不使用synchronized關鍵字來保證同步,而是直接使用Lock對象來保證同步,則系統中不存在隱式的同步監視器,也就是不使用wait(),notify()和notifyAll()方法進行線程通信,而是使用Condition對象來進行通信,它有如下三個方法:

  1. await():類似於wait(),導致當前線程等待,知道其他線程調用Condition的signal()或signalAll()來喚醒該線程。
  2. signal():類似於notify(),隨機喚醒一個線程;
  3. signalAll():喚醒在此Lock對象上等待的所有線程。
    代碼演示如下:

Account類如下,其他類和上面代碼相同:

package com.dalingjia.thead.threadCommunication.condition;


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Account {

    //使用同步鎖來保證線程同步
    private final Lock lock = new ReentrantLock();

    //使用condition對象來進行線程通信
    private final Condition condition = lock.newCondition();

    //賬號編號
    private String accountNo;
    //賬號餘額
    private Double balance;

    public Account(String accountNo, Double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    public  void draw(double drawAmount) {
        lock.lock();
        try {
            //沒錢,等待
            if(!(balance>0)) {
                condition.await();
            }else {
                balance -= drawAmount;
                System.out.println(Thread.currentThread().getName() + "<-取走:" + drawAmount + " ,餘額:" + balance);
                condition.signalAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //使用finally塊來釋放鎖
            lock.unlock();
        }

    }

    //存錢
    public synchronized void deposit(double depositAmount) {
        lock.lock();
        try {
            //賬戶有錢,等待
            if (balance > 0) {
                condition.await();
            } else {
                balance += depositAmount;
                System.out.println(Thread.currentThread().getName() + "->存進:" + depositAmount + " ,餘額:" + balance);
                condition.signalAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //使用finally塊來釋放鎖
            lock.unlock();
        }
    }
}

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

  1. BlockingQueue作爲線程同步的工具,具有一個特徵:當生產者線程試圖想BlockingQueue中放入元素時,如果該隊列已滿,則該線程阻塞;當消費者線程試圖從BlockingQueue中取出元素時,如果該隊列已空,則該線程被阻塞。
  2. BlockingQueue提供如下兩個支持阻塞的方法:
    put(E e): 嘗試把e元素放入BlockingQueue中,如果該隊列的元素已滿,則阻塞該線程。
    take(): 嘗試從BlockingQueue的頭部取出元素,如果該隊列的元素已空,則阻塞該線程。

BlockingQueue包含的方法之間的對應關係如下:
在這裏插入圖片描述
BlockingQueue代碼演示如下:

生成者:

package com.dalingjia.thead.threadCommunication.BlockingQueue;

import java.util.concurrent.BlockingQueue;

public class Producer extends Thread {

    private BlockingQueue<String> bq;

    public Producer(String name, BlockingQueue<String> bq) {
        super(name);
        this.bq = bq;
    }

    @Override
    public void run() {
        String[] strings = {"Java", "Spring", "Mysql"};
        for (int i = 0; i < 10; i++) {
            try {
                bq.put(strings[i%3]);
                System.out.println(getName() + "生成:" + bq);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消費者:

package com.dalingjia.thead.threadCommunication.BlockingQueue;

import java.util.concurrent.BlockingQueue;

public class Consumer extends Thread {

    private BlockingQueue bq ;

    public Consumer(String name, BlockingQueue bq) {
        super(name);
        this.bq = bq;
    }

    @Override
    public void run() {
        while (true) {
            try {
                bq.take();
                System.out.println(getName() + "消費:" + bq);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

測試類:

package com.dalingjia.thead.threadCommunication.BlockingQueue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class Test {

    public static void main(String[] args) {
        BlockingQueue<String> bq = new ArrayBlockingQueue<>(1);
        Producer producer = new Producer("生產者->", bq);
        Consumer cousumer = new Consumer("消費者<-", bq);
        producer.start();
        cousumer.start();
    }
}

上述測試代碼存在線程安全問題,本測試只爲加深理解BlockingQueue。

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