标签:color 线程二 定时 对应关系 SQ idt 唤醒 方法 +=
Object类提供了三个方法由同步监视器调用。分为两种情况
关于这三个方法的解释如下:
下面程序示范了存钱,取钱的两个线程。存款者和取款者不断重复存款、取钱的操作,要求存款者将钱存入指定账户后,取钱者立即取出这笔钱。不允许存款者连续两次存款,不允许取钱者两次取钱。
package com.gdut.renentrantLock; public class Account { private String accountNo; private double balance; //标识账户中是否有存款的旗标 private boolean flag = false; public Account(){} public Account(String accountNo, double balance) { this.accountNo = accountNo; this.balance = balance; } //因为账户余额不允许修改,所以只提供getter方法 public double getBalance(){ return balance; } public synchronized void draw(double drawAmount){ try { //如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞 if (!flag) { wait(); } else { System.out.println(Thread.currentThread().getName()+"取钱"+drawAmount); balance -= drawAmount; System.out.println("账户余额为:"+balance); flag = false; notifyAll(); } }catch(InterruptedException ie){ ie.printStackTrace(); } } public synchronized void desposit(double depositAmount) { try { if (flag) { //如果flag为真,表明账户中已有人存钱进去,存钱方法阻塞 wait(); } else { System.out.println(Thread.currentThread().getName() + "存款" + depositAmount); balance += depositAmount; System.out.println("账户余额为:" + balance); flag = true; notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
package com.gdut.renentrantLock; public class DrawThread extends Thread { private Account account; private double drawAmount; public DrawThread(String name,Account acount,double drawAmount){ super(name); this.account = acount; this.drawAmount = drawAmount; } public void run(){ for (int i = 0; i < 100; i++) { account.draw(drawAmount); } } }
package com.gdut.renentrantLock; public class DepositThread extends Thread{ private Account account; private double depositAmount; public DepositThread(String name,Account acount,double depositAmount){ super(name); this.account = acount; this.depositAmount = depositAmount; } public void run(){ for (int i = 0; i < 100; i++) { account.desposit(depositAmount); } } }
package com.gdut.renentrantLock; public class DrawTest { public static void main(String[] args) { Account account = new Account("1234567",0); new DrawThread("取钱者",account,800).start(); new DepositThread("存款者甲",account,800).start(); new DepositThread("存款者乙",account,800).start(); new DepositThread("存款者丙",account,800).start(); } }
效果如图:
从结果可以看出,程序最后被阻塞无法继续向下执行,这是因为3个取款者线程共有300次操作,而一个取款者只有100次取钱操作,所以程序最后被阻塞。
当使用Lock对象来保证来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程。
Condition将同步监视器方法(wait(),notify()和notifyAll())分解成截然不同的对象,以便通过将这些对象与Lock对象结合使用,为每个对象提供多个等待值(wait-set)。在这种情况下,Lock代替了同步方法或同步代码块,Condition替代了同步监视器的功能。
Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可,Condition对象提供了如下三个方法:
下面程序Account使用Lock对象来控制同步,并使用Condition对象来控制线程的协调运行。
package com.gdut.Condition; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Account { //显式定义Lock对象 private final Lock lock = new ReentrantLock(); //获得指定Lock对象的对应的Condition private final Condition cond= lock.newCondition(); private String accountNo; private double balance; //标识账户中是否有存款的旗标 private boolean flag = false; public Account(){} public Account(String accountNo, double balance) { this.accountNo = accountNo; this.balance = balance; } //因为账户余额不允许修改,所以只提供getter方法 public double getBalance(){ return balance; } public void draw(double drawAmount){ //加锁 lock.lock(); try { // 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞 if (!flag) { cond.wait(); } else { System.out.println(Thread.currentThread().getName()+"取钱"+drawAmount); balance -= drawAmount; System.out.println("账户余额为:"+balance); flag = false; cond.signalAll(); } }catch(InterruptedException ie){ ie.printStackTrace(); }finally{ lock.unlock(); } } public void desposit(double depositAmount) { //加锁 lock.lock(); try { if (flag) { //如果flag为真,表明账户中已有人存钱进去,存钱方法阻塞 cond.await(); } else { System.out.println(Thread.currentThread().getName() + "存款" + depositAmount); balance += depositAmount; System.out.println("账户余额为:" + balance); flag = true; cond.signalAll(); } } catch (InterruptedException e) { e.printStackTrace(); }finally{ lock.unlock(); } } }
该程序的其他类和执行效果完全跟上面一样。
三. 使用阻塞队列(BlockingQueue)控制线程通信
Java 5提供一个BlockingQueue接口,它是Queue的子接口,但它的主要用途并不是作为容器,而是作为线程同步的工具。BlockingQueue有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已满,则该线程被阻塞。
程序的两个线程通过交替向BlockingQueue中放入元素、取出元素,则可很好的控制线程的通信。
BlockingQueue提供如下两个支持阻塞的方法:
BlockingQueue继承了Queue接口,也可以使用Queue的方法,归纳为三类
对应关系如下:
抛出异常 | 不同返回值 | 阻塞线程 | 指定头时时长 | |
队尾插入元素 | add(E e) | offer(E e) | put(E e) | offer(e,time,unit) |
队头删除元素 | remove() | poll() | take() | poll(time,unit) |
获取、不删除元素 | element() | peek() | 无 | 无 |
BlockingQueue有5个实现类。
下面以ArrayBlockingQueue为例介绍阻塞队列的功能和用法。
package com.gdut.thread.blockingqueue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; class Producer extends Thread{ private BlockingQueue<String> bq; public Producer(BlockingQueue<String> bq){ this.bq = bq; } @Override public void run() { String[] strArr = new String[]{"java","Struts","Spring"}; for (int i = 0; i < 99 i++) { System.out.println("生产者准备生产集合元素!"); try { Thread.sleep(200); //尝试放入元素,如果队列已满,则线程被阻塞 bq.put(strArr[i%3]); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()+"生产完成:"+bq); } } } class Consummer extends Thread{ private BlockingQueue<String> bq; public Consummer(BlockingQueue<String> bq){ this.bq = bq; } @Override public void run() { while(true) { System.out.println("消费者准备消费集合元素!"); try { Thread.sleep(200); bq.take(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()+"消费完成:"+bq); } } } public class BlockingqueueTest { public static void main(String[] args) { BlockingQueue<String> bq = new ArrayBlockingQueue<>(1); //启动三个生产者线程 new Producer(bq).start(); new Producer(bq).start(); new Producer(bq).start(); //启动一个消费者线程 new Consummer(bq).start(); } }
可以看出,只要一个线程向该队列放入元素,其他生产者线程就必须等待,等待消费者线程取出队列里的元素
标签:color 线程二 定时 对应关系 SQ idt 唤醒 方法 +=
原文地址:https://www.cnblogs.com/yumiaoxia/p/9057973.html