`

多进程中的同步

    博客分类:
  • java
阅读更多

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

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;
       }
}

 




 

分享到:
评论

相关推荐

    多进程同步橘子苹果问题

    多进程同步橘子苹果问题

    Linux下多线程及多进程及同步与互斥编程详细介绍

    Linux下多线程及多进程及同步与互斥编程详细介绍

    用多进程同步方法解决生产者-消费者问题

    生产者-消费者问题是计算机科学中的一个经典问题,主要涉及多线程或多进程同步。这个问题在操作系统课程中常被用来讲解进程间的通信和同步机制。在这个场景中,我们讨论的是在Linux环境下,如何通过多进程的方式解决...

    Linux C++多进程同步锁内存共享【源代码】

    无亲缘关系多进程,使用互斥同步锁实现内存共享。

    Qt进程间通信与同步示例

    在Qt框架中,进程间通信(IPC,Inter-Process Communication)和进程同步是两个关键概念,用于在多个独立运行的程序之间交换数据和协调执行。...这个示例对于理解Qt中的并发和多进程编程具有重要的实践价值。

    多进程同步解决生产者消费者问题(c++源码)

    本节将详细介绍如何使用C++语言中的多进程同步机制来实现上述功能。 #### 1. 基础数据结构定义 首先,定义了几个基本的数据结构和变量: - `const unsigned short SIZE_OF_BUFFER = 10;` 定义了有界缓冲区的大小...

    操作系统实验四 进程同步实验

    3. 分析经典进程同步与互斥问题的解决方案:例如,哲学家就餐问题、读者写者问题等,这些经典的多进程同步问题提供了多种解决策略,如信号量、管程、事件对象等。 4. 了解Linux系统中的IPC进程同步工具:Linux提供...

    进程同步,线程同步类

    在计算机科学中,进程同步和线程同步是多任务环境下保证数据一致性与程序正确执行的重要概念。本主题将深入探讨这两个概念以及如何使用特定的同步类来实现它们,特别是使用了`Semaphore`这一同步机制。 **进程同步*...

    tongbu.zip_linux 多进程_linux 进程_多进程Linux_进程同步

    通常,它会包含创建子进程、资源分配、同步原语的使用等步骤,帮助开发者理解并实践多进程同步的概念。 总之,了解并掌握Linux下的多进程编程和同步技术对于任何系统级开发者来说都至关重要。通过合理的进程设计和...

    进程同步实验报告

    在操作系统中,进程同步是一个关键的概念,用于控制多个并发进程之间的协调与合作,确保它们能够正确、有序地访问共享资源,避免数据不一致和竞态条件。本实验报告旨在通过实际操作来深入理解和掌握进程同步的基本...

    多进程同步-生产者消费者模式-C实现

    在计算机科学中,多进程同步是一个关键的概念,用于管理和协调多个并发执行的进程,确保它们在访问共享资源时不会产生冲突。在这个场景下,我们关注的是一个经典的并发编程模型——生产者消费者模式。该模式是多进程...

    linux下用多进程同步方法解决生产者-消费者问题源代码

    理解并能正确实施这种同步策略对于编写高效、可靠的多进程程序至关重要。这不仅有助于避免数据竞争和死锁,还能提高系统资源的利用率,使得生产者和消费者可以按需工作,避免不必要的等待。通过分析和研究提供的源...

    操作系统进程同步

    进程同步是操作系统中一个核心概念,旨在解决多进程间资源竞争的问题,确保多个进程能够有序地访问共享资源,防止数据不一致性和死锁等问题的发生。进程同步机制通常通过信号量、互斥锁、条件变量等手段实现。 ####...

    进程同步C语言实验

    根据给定的文件信息,我们可以深入探讨进程同步在操作系统中的应用以及如何通过C语言实现这一概念。进程同步是操作系统中的一个关键概念,它确保多个进程能够协调地共享资源或信息,避免冲突,维持数据一致性。在多...

    进程同步与互斥

    在实际编程中,我们需要特别注意死锁(Deadlock)问题,即两个或更多进程因互相等待对方释放资源而陷入无限等待的状态。避免死锁的方法包括:资源预分配、死锁预防、死锁避免和死锁检测与恢复策略。C语言中虽然没有...

    redis实现多进程数据同步工具代码分享

    在本文中,我们将深入探讨如何使用Redis来实现多进程数据同步工具,并理解给出的Java代码片段。Redis是一个高性能的键值存储系统,常用于数据缓存、消息队列以及分布式环境下的数据同步。多进程数据同步是确保在多个...

    进程同步经典程序MFC

    在计算机科学领域,进程同步是操作系统中的一个核心概念,它涉及到多线程和多进程环境下的资源管理和协调。MFC(Microsoft Foundation Classes)是微软提供的一套C++库,用于构建Windows应用程序,其中包括对进程...

    进程的同步与互斥习题(含部分题目的参考答案).doc

    在缓冲区问题中,我们可以看到多个进程之间的同步和互斥关系。例如,在问题 1 中,我们有一个输入进程和一个输出进程,它们共享一个缓冲区。缓冲区中每次只能存放一个数,输入进程负责输入数据,而输出进程负责输出...

    进程通信与同步

    进程通信与同步是操作系统中的重要概念,特别是在多线程或多进程环境下,它们是确保程序正确性和性能的关键。这里我们将深入探讨这两个概念,并结合C++实现进行解析。 **进程通信** 进程通信(Process ...

Global site tag (gtag.js) - Google Analytics