多进程中的同步

一个竞争的例子:下面的程序是模拟一个有若干账户的银行,每个账户随机地向其他账户转钱。

bank.java有若干账户,账户之间可以相互汇钱。


public class Bank {
       private final double[] accounts;
       
       /*
        * Construct the bank
        * @param n the number of the accounts
        * @param initialBalance the initial balance of each account
        */
       public Bank(int n,double initialBalance){
    	   accounts=new double[n];
    	   for(int i=0;i<n;i++){
    		   accounts[i]=initialBalance;
    	   }
       }
       
       /*
        * transfer money from one to another
        * @param from the account to transfer from
        * @param to the account to transfer to
        * @param amount the amount to transfer
        */
       public void transfer(int from,int to,double amount){
    	   if(accounts[from]<amount)
    		   return;
    	   System.out.print(Thread.currentThread());
    	   accounts[from]-=amount;
    	   accounts[to]+=amount;
    	   System.out.println("   "+from+"-->"+to+"   total:"+getTotaleBalance());
       }
       
       /*
        * get total money of the bank
        * @return the total balance
        */
       public double getTotaleBalance(){
    	   double sum=0;
    	   for(double a:accounts)
    	   		sum+=a;
    	   return sum;
       }
       
       /*
        * get the number of the accounts in the bank
        * @return the number of the accounts
        */
       public int size(){
    	   return accounts.length;
       }
}

 TransferRunnable.java为Bank b中的每个accounts[from]创建了一个线程


public class TransferRunnable implements Runnable{
	
	private static final int DELAY = 10;
	private Bank bank;
	private int fromAccount;
	private double maxAmount;
	/*
	 * construct a transfer runnable
	 * @param b the bank to transfer
	 * @param from the account the transfer form
	 * @param max the maximum amount money to transfer
	 */
	public TransferRunnable(Bank b,int from,double max){
		bank=b;
		fromAccount=from;
		maxAmount=max;
	}
	
	public void run() {
		try{
		    while(true){
		    	  int to=(int)(bank.size()*Math.random());
		     	 double amount =maxAmount*Math.random();
			     bank.transfer(fromAccount, to, amount);
			     Thread.sleep( (long) ((int)DELAY*Math.random()));
		   }
		}catch(InterruptedException e){
			
		}
	}

}

 测试UnsysnchBankTest.java


public class UnsysnchBankTest {
    private static final int NOACCOUNTS = 100;
	private static final double INITIAL_BALANCE = 1000;

	public static void main(String[] args){
    	Bank b=new Bank(NOACCOUNTS,INITIAL_BALANCE);
    	for(int i=0;i<NOACCOUNTS;i++){
    		TransferRunnable r=new TransferRunnable(b,i,INITIAL_BALANCE);
    		Thread t=new Thread(r);
    		t.start();
    	}
    }
}

 

实验一段时间后发现:

Thread[Thread-54,5,main]   54-->87   total:98917.75963258508

Thread[Thread-18,5,main]   18-->23   total:98917.75963258508

Thread[Thread-28,5,main]   28-->65   total:98917.75963258505

Thread[Thread-98,5,main]   98-->39   total:98917.75963258505

    交易一段时间以后,银行总金额不足100000。

    问题出现在当两个线程同时更行一个账户时,可能一个线程把另外一个线程的数据给察去了。例如,两个线程同时执行

        accounts[to]+=amount;

    可能执行顺序为:

        线程1:读取accounts[to],假设为5000;增加amount后,accounts[to]应该为5500,但是5500还来不及返回到accounts[to]中,此时,线程2获得执行权力,读取accounts[to]为5000,增加并返回,假设此时accounts[to]为5900,然后再执行线程1的写回步骤,将accounts[to]改为5500.这个过程中accounts[to]少了900。


有两种机制可以防止代码块并发访问的干扰:

1、锁对象和条件对象

锁对象ReentrantLock保护代码块的基本结构如下:

myLock.lock();//a ReentrantLock object

try{

    critical section

}finally{

    myLock.unlock();

}

这个结构确保任何时刻只有一个线程进入临界区,一旦一个线程封锁了锁对象,其他线程调用lock时被阻塞。

用一个锁保护Bank中的transfer方法如下:

        Class Bank{

           private ReentrantLock bankLock;

             public void transfer(int from,int to,double amount) throws InterruptedException{

                      bankLock.lock();

                      try{

                            System.out.print(Thread.currentThread());

                            accounts[from]-=amount;

                            accounts[to]+=amount;

                            System.out.println("   "+from+"-->"+to+"   total:"+getTotaleBalance());

                    }finally{

                            bankLock.unlock();

                  }

             }

       }

     每一个bank对象都有自己的ReentrantLock对象。如果两个线程试图访问同一个Bank对象,那么锁以串行方式提供服务。但是,如果两个线程访问不同的Bank对象,每个线程得到不同的锁,他们不会发生阻塞。

      锁是可重入的,线程可以重复获取自己持有的锁,所保持一个持有基数来跟踪对lock方法的嵌套调用。Transfer方法调用getTotalBalance,这也会封锁bankLock对象,此时bankLock持有计数为2,当getTotalBalance返回时,持有计数变回1,当transfer方法退出时持有计数为0

 

条件对象

      线程进入临界区,却发现某一条件满足之后它才能执行。要使用一个条件对象来管理那些已经获得但不能做有用工作的线程。条件对象常常被称为条件变量。如下代码:

if(accounts[from]>amount)

        bank.transfer(from,to,amount);

     当前线程完全可能执行if语句,发现可以执行第二句,但是线程被中断,其他线程可能使accounts[from] 中的数据变得小于amount,但是原来的线程再次运行后执行第二句,但此时条件实际上已经不满足了。

一个锁对象可以有一个或多个相关的条件对象,可以用bankLock.newCondition()来获得一个条件对象。

      private ReentrantLock bankLock;

      private Condition sufficientFunds;

      public void transfer(int from,int to,double amount) throws InterruptedException{

              bankLock.lock();

              try{

                     while(accounts[from]<amount)

                         sufficientFunds.await();

                  … …

                  sufficientFunds.signalAll();

              }finally{

                     bankLock.unlock();

              }

        }

      当发现余额不足时,调用await()方法,阻塞当前线程,并放弃锁。一段满足条件,并且接收到同一条件上的singleAll方法后,它变得可运行,当有锁可用时他继续执行。

 

改进后的Bank.java


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

public class Bank {
       private final double[] accounts;
	private ReentrantLock bankLock;
	private Condition sufficientFunds;
       
       /*
        * Construct the bank
        * @param n the number of the accounts
        * @param initialBalance the initial balance of each account
        */
       public Bank(int n,double initialBalance){
    	   accounts=new double[n];
    	   for(int i=0;i<n;i++){
    		   accounts[i]=initialBalance;
    	   }
    	   bankLock=new ReentrantLock();
    	   sufficientFunds=bankLock.newCondition();
       }
       
       /*
        * transfer money from one to another
        * @param from the account to transfer from
        * @param to the account to transfer to
        * @param amount the amount to transfer
        */
       public void transfer(int from,int to,double amount) throws InterruptedException{
    	   bankLock.lock();
    	   try{
    		   while(accounts[from]<amount)
        		   sufficientFunds.await();
        	   System.out.print(Thread.currentThread());
        	   accounts[from]-=amount;
        	   accounts[to]+=amount;
        	   System.out.println("   "+from+"-->"+to+"   total:"+getTotaleBalance());
        	   sufficientFunds.signalAll();
    	   }finally{
    		   bankLock.unlock();
    	   }
    	   
       }
       
       /*
        * get total money of the bank
        * @return the total balance
        */
       public double getTotaleBalance(){
    	   bankLock.lock();
    	   try{
    		   double sum=0;
        	   for(double a:accounts)
        	   		sum+=a;
        	   return sum;
    	   }finally{
    		   bankLock.unlock();
    	   }
    	   
       }
       
       /*
        * get the number of the accounts in the bank
        * @return the number of the accounts
        */
       public int size(){
    	   return accounts.length;
       }
}

 

2、synchronized关键字

     synchronize关键字的原理是每一个java对象都有一个内部锁。也就是说

          public synchronized  void method(){... ...}

     相当于:

          public void method(){

             this.intrinsicLock.lock();

             try{

                  ... ...

             }finally{

                  his.intrinsicLock.unlock();

             }

        }

     内部锁只有一个相关条件,wait/notifyAll相当于intrinsicCondition.await()/intrinsicCondition. signalyAll ()。用synchronized关键字的Bank.java代码如下:


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

public class Bank {
       private final double[] accounts;
       
       /*
        * Construct the bank
        * @param n the number of the accounts
        * @param initialBalance the initial balance of each account
        */
       public Bank(int n,double initialBalance){
    	   accounts=new double[n];
    	   for(int i=0;i<n;i++){
    		   accounts[i]=initialBalance;
    	   }
       }
       
       /*
        * transfer money from one to another
        * @param from the account to transfer from
        * @param to the account to transfer to
        * @param amount the amount to transfer
        */
       public synchronized void transfer(int from,int to,double amount) throws InterruptedException{
    	  while(accounts[from]<amount)
        		     wait();
        	   System.out.print(Thread.currentThread());
        	   accounts[from]-=amount;
        	   accounts[to]+=amount;
        	   System.out.println("   "+from+"-->"+to+"   total:"+getTotaleBalance());
        	       notifyAll();
    	   
       }
       
       /*
        * get total money of the bank
        * @return the total balance
        */
       public synchronized double getTotaleBalance(){
    		   double sum=0;
        	   for(double a:accounts)
        	   		sum+=a;
        	   return sum;
       }
       
       /*
        * get the number of the accounts in the bank
        * @return the number of the accounts
        */
       public int size(){
    	   return accounts.length;
       }
}

 




 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值