Java多线程同步中的两个非凡类
副标题#e#
Java语言内置了synchronized要害字用于对多线程举办同步,大大利便了Java中多线程措施的编写。可是仅仅利用synchronized要害字还不能满意对多线程举办同步的所有需要。各人知道,synchronized仅仅可以或许对要领可能代码块举办同步,假如我们一个应用需要超过多个要领举办同步,synchroinzed就不能胜任了。在C++中有许多同步机制,好比信号量、互斥体、临届区等。在Java中也可以在synchronized语言特性的基本上,在更高条理构建这样的同步东西,以利便我们的利用。
当前,广为利用的是由Doug Lea编写的一个Java中同步的东西包,可以在这儿相识更多这个包的具体环境:
http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
该东西包已经作为JSR166正处于JCP的节制下,即将作为JDK1.5的正式构成部门。本文并不规划具体分解这个东西包,而是对多种同步机制的一个先容,同时给出这类同步机制的实例实现,这并不是家产级的实现。但个中会参考Doug Lea的这个同步包中的家产级实现的一些代码片段。
本例中还沿用上篇中的Account类,不外我们这儿编写一个新的ATM类来模仿自动提款机,通过一个ATMTester的类,生成10个ATM线程,同时对John账户举办查询、提款和存款操纵。Account类做了一些窜改,以便适应本篇的需要:
import java.util.HashMap;
import java.util.Map;
class Account
{
String name;
//float amount;
//利用一个Map模仿耐久存储
static Map storage = new HashMap();
static
{
storage.put("John", new Float(1000.0f));
storage.put("Mike", new Float(800.0f));
}
public Account(String name)
{
//System.out.println("new account:" + name);
this.name = name;
//this.amount = ((Float)storage.get(name)).floatValue();
}
public synchronized void deposit(float amt)
{
float amount = ((Float)storage.get(name)).floatValue();
storage.put(name, new Float(amount + amt));
}
public synchronized void withdraw(float amt)
throws InsufficientBalanceException
{
float amount = ((Float)storage.get(name)).floatValue();
if (amount >= amt) amount -= amt;
else throw new InsufficientBalanceException();
storage.put(name, new Float(amount));
}
public float getBalance()
{
float amount = ((Float)storage.get(name)).floatValue();
return amount;
}
}
在新的Account类中,我们回收一个HashMap来存储账户信息。Account由ATM类通过login登录后利用:
public class ATM
{
Account acc;
//作为演示,省略了暗码验证
public boolean login(String name)
{
if (acc != null) throw new IllegalArgumentException("Already logged in!");
acc = new Account(name);
return true;
}
public void deposit(float amt)
{
acc.deposit(amt);
}
public void withdraw(float amt) throws InsufficientBalanceException
{
acc.withdraw(amt);
}
public float getBalance()
{
return acc.getBalance();
}
public void logout ()
{
acc = null;
}
}
#p#副标题#e#
下面是ATMTester,在ATMTester中首先生成了10个ATM实例,然后启动10个线程,同时登录John的账户,先查询余额,然后,再提取余额的80%,然后再存入等额的款(以维持最终的余额的稳定)。凭据我们的预想,应该不会产生金额不敷的问题。首先看代码:
public class ATMTester
{
private static final int NUM_OF_ATM = 10;
public static void main(String[] args)
{
ATMTester tester = new ATMTester();
final Thread thread[] = new Thread[NUM_OF_ATM];
final ATM atm[] = new ATM[NUM_OF_ATM];
for (int i=0; i<NUM_OF_ATM; i++)
{
atm[i] = new ATM();
thread[i] = new Thread(tester.new Runner(atm[i]));
thread[i].start();
}
}
class Runner implements Runnable
{
ATM atm;
Runner(ATM atm)
{
this.atm = atm;
}
public void run()
{
atm.login("John");
//查询余额
float bal = atm.getBalance();
try
{
Thread.sleep(1);
//模仿人从查询到取款之间的隔断
}
catch (InterruptedException e)
{ // ignore it }
try
{
System.out.println("Your balance is:" + bal);
System.out.println("withdraw:" + bal * 0.8f);
atm.withdraw(bal * 0.8f);
System.out.println("deposit:" + bal * 0.8f);
atm.deposit(bal * 0.8f);
}
catch (InsufficientBalanceException e1)
{
System.out.println("余额不敷!");
}
finally
{ atm.logout(); }
}
}
}
运行ATMTester,功效如下(每次运行功效都有所差别):
Your balance is:1000.0
withdraw:800.0
deposit:800.0
Your balance is:1000.0
Your balance is:1000.0
withdraw:800.0
withdraw:800.0
余额不敷!
Your balance is:200.0
Your balance is:200.0
Your balance is:200.0
余额不敷!
Your balance is:200.0
Your balance is:200.0
Your balance is:200.0
Your balance is:200.0
withdraw:160.0
withdraw:160.0
withdraw:160.0
withdraw:160.0
withdraw:160.0
withdraw:160.0
withdraw:160.0
deposit:160.0
余额不敷!
余额不敷!
余额不敷!
余额不敷!
余额不敷!
余额不敷!
#p#分页标题#e#
为什么会呈现这样的环境?因为我们这儿是多个ATM同时对同一账户举办操纵,好比一个ATM查询出了余额为1000,第二个ATM也查询出了余额1000,然后两者都期望提取出800,那么只有第1个用户可以或许乐成提出,因为在第1个提出800后,账户真实的余额就只有200了,而第二个用户仍认为余额为1000。这个问题是由于多个ATM同时对同一个账户举办操纵所不行制止发生的效果。要办理这个问题,就必需限制同一个账户在某一时刻,只能由一个ATM举办操纵。如何才气做到这一点?直接通过synchronized要害字可以吗?很是遗憾!因为我们此刻需要对整个Account的多个要领举办同步,这是超过多个要领的,而synchronized仅能对要领可能代码块举办同步。
我们首先开拓一个BusyFlag的类,雷同于C++中的Simaphore。
public class BusyFlag
{
protected Thread busyflag = null;
protected int busycount = 0;
public synchronized void getBusyFlag()
{
while (tryGetBusyFlag() == false)
{
try
{
wait();
}
catch (Exception e) {}
}
}
private synchronized boolean tryGetBusyFlag()
{
if (busyflag == null)
{
busyflag = Thread.currentThread();
busycount = 1;
return true;
}
if (busyflag == Thread.currentThread())
{
busycount++; return true;
}
return false;
}
public synchronized void freeBusyFlag()
{
if(getOwner()== Thread.currentThread())
{
busycount--;
if(busycount==0)
{
busyflag = null;
notify();
}
}
}
public synchronized Thread getOwner()
{
return busyflag;
}
}
注:参考Scott Oaks & Henry Wong《Java Thread》
BusyFlag有3个果真要领:getBusyFlag, freeBusyFlag, getOwner,别离用于获取忙符号、释放忙符号和获取当前占用忙符号的线程。利用这个BusyFlag也很是地简朴,只需要在需要锁定的处所,挪用BusyFlag的getBusyFlag(),在对锁定的资源利用完毕时,再挪用改BusyFlag的freeBusyFlag()即可。下面我们开始改革前面中的Account和ATM类,并应用BusyFlag东西类使得同时只有一个线程可以或许会见同一个账户的方针得以实现。首先,要改革Account类,在Account中内置了一个BusyFlag工具,并通过此符号工具对Account举办锁定息争锁:
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
class Account
{
String name;
//float amount;
BusyFlag flag = new BusyFlag();
//利用一个Map模仿耐久存储
static Map storage = new HashMap();
static
{
storage.put("John", new Float(1000.0f));
storage.put("Mike", new Float(800.0f));
}
static Map accounts = Collections.synchronizedMap(new HashMap());
private Account(String name)
{
this.name = name;
//this.amount = ((Float)storage.get(name)).floatValue();
}
public synchronized static Account getAccount (String name)
{
if (accounts.get(name) == null) accounts.put(name, new Account(name));
return (Account) accounts.get(name);
}
public synchronized void deposit(float amt)
{
float amount = ((Float)storage.get(name)).floatValue();
storage.put(name, new Float(amount + amt));
}
public synchronized void withdraw(float amt) throws InsufficientBalanceException
{
float amount = ((Float)storage.get(name)).floatValue();
if (amount >= amt) amount -= amt;
else throw new InsufficientBalanceException();
storage.put(name, new Float(amount));
}
public float getBalance()
{
float amount = ((Float)storage.get(name)).floatValue(); return amount;
}
public void lock()
{
flag.getBusyFlag();
}
public void unlock()
{
flag.freeBusyFlag();
}
}
#p#分页标题#e#
新的Account提供了两个用于锁定的要领:lock()和unlock(),供Account工具的客户端在需要时锁定Account息争锁Account,Account通过委托给BusyFlag来提供这个机制。别的,各人也发明白,新的Account中提供了对Account工具的缓存,同时去除了public的结构要领,改为利用一个静态工场要领供用户获取Account的实例,这样做也是有须要的,因为我们但愿所有的ATM机同时只能有一个可以或许对同一个Account举办操纵,我们在Account上的锁定是对一个特定Account工具举办加锁,假如多个ATM同时实例化多个同一个user的Account工具,那么仍然可以同时操纵同一个账户。所以,要利用这种机制就必需担保Account工具在系统中的独一性,所以,这儿利用一个Account的缓存,并将Account的结构要领变为私有的。你也可以说,通过在Account类锁长举办同步,即将Account中的BusyFlag工具声明为static的,但这样就使同时只能有一台ATM机举办操纵了。这样,在一台ATM机在操纵时,全市其它的所有的ATM机都必需期待。