java 多线程(三)条件对象
转载请注明出处:http://blog.csdn.net/xingjiarong/article/details/47417383
在上一篇博客中,我们学会了用ReentrantLock来控制线程訪问同一个数据,防止出现Race Condition。这一次呢。我们继续深入的学习,学习一下java中的条件对象。条件对象在多线程同步中用到的比較多。
首先,我们来介绍一下临界区。
临界区:在同步的程序设计中。临界区指的是一个訪问共用资源的程序片段,而这些共用资源又具有无法同一时候被多个线程訪问的特性。 当有线程进入临界区时,其它线程或是进程必须等待,在一些情况下必须在临界区的进入点与离开点採用一些特殊的方法。以确保这些共用资源是被相互排斥使用的。
如今我们来看一个新的样例,这是一个银行转账的样例。在Bank类中,我们声明了一个10个大小的数组,用来表示银行中的10个账户。而相应的数值就是这个账户中相应的金额。Bank类提供了转移账户资金的方法,能够从from账户,转移amount的资金到to账户。还提供了获得这些账户的总金额的方法,getTotalBalabce(),由于资金转移是发生在这10个账户中的,所以不管如何转移。总的金额应该是不变的。
Bank.java
import java.util.concurrent.locks.ReentrantLock;
public class Bank {
/**
* 利用数组模拟银行账户
*/
private final double accounts[];
private ReentrantLock lock = new ReentrantLock();
public Bank() {
accounts = new double[10];
/*
* 初始化,使每一个账户都初始有initialBalance金钱
*/
for (int i = 0; i < 10; i++)
accounts[i] = 1000;
}
/**
* 资金转移的方法
*
* @param from
* 源账户
* @param to
* 目标账户
* @param amount
* 转移金额
*/
public void transfer(int from, int to, double amount) {
lock.lock();
try {
if (accounts[from] < amount)
return
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf("%5.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance:%5.2f\n", getTotalBalance());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 获取全部账户的总金额
*
* @return
*/
public double getTotalBalance() {
lock.lock();
double sum = 0;
try {
for (double a : accounts)
sum += a;
} finally {
lock.unlock();
}
return sum;
}
/**
* 获取如今账户的数量
*
* @return
*/
public int size() {
return accounts.length;
}
}
TransferRunnable这个类实现了Runnable接口,我们在Main函数中拿他做线程,它的run方法就是产生一个随机的目标账户和转移的金额。然后调用Bank类的方法将资金转移到目标账户中去。
TransferRunnable.java
public class TransferRunnable implements Runnable{
private Bank bank;
private int fromAccount;
public TransferRunnable(Bank b,int from){
bank=b;
fromAccount=from;
}
public void run(){
try{
while(true)
{
int toAccount = (int)(bank.size()*Math.random());
double amount = 1000*Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep(1000);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
在主程序中,我们创建了10个线程,每一个线程都从一个相应的账户转移一定的资金到随机的账户中去。
Main.java
public class Main {
public static void main(String[] args) {
Bank b = new Bank();
Thread[] threadArray = new Thread[10];
for (int i = 0; i < 10; i++) {
threadArray[i] = new Thread(new TransferRunnable(b, i));
}
for(int i=0;i<10;i++){
threadArray[i].start();
}
}
}
在这个程序中有共同的变量——10个银行账户的资金,所以就有相应的临界区——Bank类的transfer方法和getTotalBalabce方法,假设不正确临界区加以保护的话,会导致多个线程同一时候进入临界区,而导致结果错误,读者能够试验一下,将ReentrantLock加锁的地方都去掉,看看总金额是不是会产生变化,在我的试验中,不但总金额的数量不正确。就连打印的语句顺序都是不正确的,说明一个线程在System.out.printf()的过程中都被别的线程打断了。
看了这个样例,相信大家对ReentrantLock的使用更加熟练了。
如今我们来考虑这样一个问题,大家看到transfer方法中,假设账户中的金额不足的时候就立马返回而不再发生资金转移,我如今不想这样处理了。我想要这样处理,假设账户资金不足的话,就一直等待,直到其它的账户向这个账户转移足够的资金了。然后再发生资金转移。
问题是。操作这个账户的线程已经获得了lock,其它的线程无法再进入lock方法,也就不可能向这个账户转移相应的资金了,所以说,这个线程必须放弃lock的控制权,让其它的线程获得。
Java中提供了条件对象,Condition类,来配合ReentrantLock实现对临界区的控制,我们对Bank类做以下的改变。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Bank {
/**
* 利用数组模拟银行账户
*/
private final double accounts[];
private ReentrantLock lock = new ReentrantLock();
private Condition condition;
public Bank() {
accounts = new double[10];
/*
* 初始化,使每一个账户都初始有initialBalance金钱
*/
for (int i = 0; i < 10; i++)
accounts[i] = 1000;
condition = lock.newCondition();
}
/**
* 资金转移的方法
*
* @param from
* 源账户
* @param to
* 目标账户
* @param amount
* 转移金额
*/
public void transfer(int from, int to, double amount) {
lock.lock();
try {
while (accounts[from] < amount)
condition.await();
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf("%5.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance:%5.2f\n", getTotalBalance());
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 获取全部账户的总金额
*
* @return
*/
public double getTotalBalance() {
lock.lock();
double sum = 0;
try {
for (double a : accounts)
sum += a;
} finally {
lock.unlock();
}
return sum;
}
/**
* 获取如今账户的数量
*
* @return
*/
public int size() {
return accounts.length;
}
}
首先,声明了一个Condition类的对象引用变量condition,在Bank的构造方法中进行的初始化。初始化是是调用的ReentrantLock类的方法newCondition(),说明Condition对象时依赖于ReentrantLock对象的。一个ReentrantLock对象能够创建多个条件对象。名字通常以控制的条件来命名。
然后在transfer方法中,以下两行是关键:
while (accounts[from] < amount)
condition.await();
用while循环检測账户的剩余金额,假设剩余金额不足就调用await()方法,await()会堵塞线程,并释放线程保持的锁。这时其它的线程就能够进入临界区了,当账户的剩余金额满足条件时,等待的线程也不会主动的唤醒,直到有一个线程调用了signalAll()方法。signalAll()方法唤醒全部由于不满足条件而等待的线程,可是线程不一定能够继续向下运行,由于signalAll()唤醒线程时,并非告诉线程你的条件满足了,能够继续向下运行了,而是告诉线程,如今条件改变,你能够又一次检測条件是否满足了。假设条件被满足。那么也唯独一个线程能够继续向下运行。由于一旦一个对象获得了临界区的锁,其它的线程就不能再进入临界区了。
那么应当什么时候调用signalAll()方法呢,从经验上将应该在对象的状态有利于等待线程的方向改变时调用。
这里我们再发生资金转移后调用,由于资金转移后。其它的线程有可能满足条件了。
注意:通常。await()的调用应该在例如以下形式的循环体中:
while(!(ok to proceed))
condition.await();
这里的while循环不能换成if条件语句,由于被别的线程用signalAll方法唤醒的线程,不过条件可能满足了,而不是条件一定满足了。假设不用while循环继续检測的话,就会造成条件不满足的线程继续向下运行,从而产生错误。
当一个线程拥有某个条件的锁时。它只能够在该条件上调用await(),signalAll()。signal()这三个方法。如今我们讲讲第三个方法。
signal和signalAll的差别是,signal不会将全部的等待线程唤醒。而是随机选择一个线程唤醒,而signalAll是将全部的等待线程都唤醒。
最后附上源代码:http://download.csdn.net/detail/xingjiarong/9010675
在下一篇博客里。我会为大家介绍java的synchronized关键字,希望与大家一起学习一起进步,请大家继续关注我的博客,假设大家支持我的话,就顶我一下吧。
java 多线程(三)条件对象的更多相关文章
- java 多线程三
java 多线程一 java 多线程二 java 多线程三 java 多线程四 注意到 java 多线程一 中 MyThread2 运行结果出现0.-1,那是因为在操作共享数据时没有加锁导致. 加锁的 ...
- 从火箭发场景来学习Java多线程并发闭锁对象
从火箭发场景来学习Java多线程并发闭锁对象 倒计时器场景 在我们开发过程中,有时候会使用到倒计时计数器.最简单的是:int size = 5; 执行后,size—这种方式来实现.但是在多线程并发的情 ...
- Java多线程操作同一个对象,线程不安全
Java多线程操作同一个对象 发现问题:多个线程操作同一资源的情况下,线程不安全,数据紊乱 代码: package multithreading; // Java多线程操作同一个对象 // 买火车票的 ...
- java多线程三之线程协作与通信实例
多线程的难点主要就是多线程通信协作这一块了,前面笔记二中提到了常见的同步方法,这里主要是进行实例学习了,今天总结了一下3个实例: 1.银行存款与提款多线程实现,使用Lock锁和条件Condition. ...
- Java多线程——<三>简单的线程执行:Executor
一.概述 按照<Java多线程——<一><二>>中所讲,我们要使用线程,目前都是显示的声明Thread,并调用其start()方法.多线程并行,明显我们需要声明多个 ...
- java多线程(三)-Executors实现的几种线程池以及Callable
从java5开始,类库中引入了很多新的管理调度线程的API,最常用的就是Executor(执行器)框架.Executor帮助程序员管理Thread对象,简化了并发编程,它其实就是在 提供了一个中间层, ...
- 从零开始学习Java多线程(三)
本文主要对Java多线程同步与通信以及相关锁的介绍. 1 .Java多线程安全问题 Java多线程安全问题是实现并发最大的问题,可以说多线程开发其实就是围绕多线程安全问题开发,涉及之深,不是简简单单一 ...
- Java多线程——Condition条件
简介 Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signa ...
- Java多线程对同一个对象进行操作
示例: 三个窗口同时出售20张票. 程序分析: 1.票数要使用一个静态的值. 2.为保证不会出现卖出同一张票,要使用同步锁. 3.设计思路:创建一个站台类Station,继承THread,重写run方 ...
- java多线程系列(二)---对象变量并发访问
对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...
随机推荐
- https://github.com/ 英文库
https://github.com/ https://github.com/sachinchoolur
- Android手机间使用socket进行文件互传实例
这是一个Android手机间文件传输的例子,两个手机同时装上此app,然后输入接收端的ip,选择文件,可以多选,点确定,就发送到另一个手机,一个简单快捷文件快传实例.可以直接运用到项目中. 下面是文件 ...
- JavaScript--Module模式
//module: Module模式是JavaScript编程中一个非常通用的模式 window.onload = function() { //1.基本使用: var MyFn = function ...
- 洛谷 P1303 A*B Problem
P1303 A*B Problem 题目描述 求两数的积. 输入输出格式 输入格式: 两行,两个数. 输出格式: 积 输入输出样例 输入样例#1: 复制 1 2 输出样例#1: 复制 2 说明 每个数 ...
- POJ 2253-Frogger (Prim)
题目链接:Frogger 题意:两仅仅青蛙,A和B,A想到B哪里去,可是A得弹跳有限制,所以不能直接到B,可是有其它的石头作为过渡点,能够通过他们到达B,问A到B的全部路径中.它弹跳最大的跨度的最小值 ...
- openstack之虚拟机创建流程分析
这篇博文静静的呆在草稿箱大半年了.假设不是由于某些原因被问到,以及由于忽略它而导致的损失,否则我也不知道什么时候会将它完毕.感谢这段时间经历的挫折,让我知道不足.希望你能给我更大的决心! 本文试图具体 ...
- SQL-android uri的使用(转载)
今天在操作android的时候,用到了数据库的访问,就在网上学习了一下关于数据库的知识.其中访问数据库就是通过uri进行的,所以这里总结下android uri的应用. 以下内容参考http://ww ...
- drawable-实现图片旋转
今天因为需要,所以要让一个图片随着某种需要进行旋转.但是,又不能一张张的做动态图片.所以就在网上找了这么个方法.但是,这个方法有个问题,就是虽然能实现图片的旋转.但是,图片旋转以后会进行缩放.具体原因 ...
- h5背景
1.背景属性复习: background-image background-color background-repeat background-position background-attachm ...
- 00089_字节输出流OutputStream
1.字节输出流OutputStream (1)OutputStream此抽象类,是表示输出字节流的所有类的超类.操作的数据都是字节,定义了输出字节流的基本共性功能方法: (2)输出流中定义都是写wri ...