多线程未同步可能导致的问题及其解决方案

4 篇文章 0 订阅

这是一个来自java的例子,我觉得很典型,就放上来谈谈.
下面的示例来自 "Java核心技术 第二卷 高级特性"

在下面的测试程序中,我们模拟一个拥有一定数量账户的银行.我们随机的产生把钱在不同账号之间转移的交易.每个账号都有一个线程,在每笔交易中,都会从线程所服务的账户中随机取出一定数额的金钱到另一个随机账户中.

我们有一个Bank类,它有一个transfer方法,这个方法将一定数额的钱从一个账户转移到另外一个账户.如果源账户没有足够的金额,该方法将直接返回.

public   void  transfer( int  from, int  to, double  amount) {
        
if(accounts[from] < amount) return;
        System.
out.print(Thread.currentThread());
        accounts[from] 
-= amount;
        System.
out.printf("%10.2f from %d to %d",amount,from,to);
        accounts[to] 
+= amount;
        System.
out.printf("Total Balance: %10.2f%n", getTotalBalance());
    }

下面是TransferRunnable类的代码.他的run方法不断的从一个固定账户中取出钱.在每次迭代中,run方法随机挑选一个目标账户和一个随机账户,调用Bank对象的transder方法,然后thread.sleep();
  public   void  run() {
        
try{
            
while(true){
                
int toAccount = (int)(bank.size() * Math.random());
                
double amount = maxAmount * Math.random();
                bank.transfer(fromAccount,toAccount,amount);
                Thread.sleep((
int)(DELAY * Math.random()));
            }

        }

        
catch(InterruptedException e){}
    }


在这个模拟程序运行时,我们不知道在某个时间某个银行账户里有多少钱.但我们知道所有账户中的金额总量保持不变,因为我们所做的只是把钱在账户之间转移.

下面是典型的输出:
...
Thread[Thread-29,5,main]    356.69 from 29 to 39Total Balance:  98847.71
Thread[Thread-12,5,main]    833.65 from 12 to 89Total Balance:  99690.69
Thread[Thread-0,5,main]    809.40 from 0 to 60Total Balance:  99774.88
Thread[Thread-80,5,main]    436.67 from 80 to 57Total Balance:  99206.67
...

就想你看到的那样,出现了错误.金额总量发生了细微的变化.

这个问题是在多个线程试图同时更新账户时出现的.假设两个线程同时执行这条指令:
accounts[to] += amount;
问题在于他不是原子操作.指令可能会以下面这种方式执行:
1)将account[to]载入寄存器.
2)增加amount.
3)将结果写回accounts[to].
        现在,假设第一个线程执行到了第一步和第二步,然后被中断了.而此时第二个线程被唤醒并更新了account数组中的同一项.接着第一个线程被唤醒并完成了第三步.
        这样,第二个线程所做的更新就被抹去了.结果导致总金额不再正确.

那么这个问题应该怎么解决?

从jdk 5.0开始,有两种机制来保护代码块不受并行访问的干扰.
1)使用ReentraltLock类,
         用ReentraltLock保护代码块的基本结构如下
myLock.lock();
try{
   critical section
}
finally{
   myLock.unlock();
}
这种结构保证在任何时刻只能有一个线程能够进入临界区.一旦一个线程锁住了锁对象,其他任何线程都无法通过lock语句.当其他线程调用lock时,他们会被被阻塞,直到第一个线程释放锁对象.
         让我们使用锁对象来保护Bank类的transfer方法.
修改Bank类

  private  Lock bankLock  =   new  ReentrantLock();
    
public   void  transfer( int  from, int  to, double  amount) {
        bankLock.
lock();
        
try{
        
if(accounts[from] < amount) return;
            System.
out.print(Thread.currentThread());
            accounts[from] 
-= amount;
            System.
out.printf("%10.2f from %d to %d",amount,from,to);
            accounts[to] 
+= amount;
            System.
out.printf("Total Balance: %10.2f%n", getTotalBalance());
        }

        
finally{
            bankLock.unlock();
        }

    }

再次运行程序,现在可以不会出现问题了.
另外还需要注意的是:
必须小心处理,以防临界区中的代码因为抛出了一个异常而掉出临界区.如果一个异常在临界区代码结束前抛出,那么finally子句就会释放锁,但这会使对象处在某种受损状态.

2)使用Synchronized关键字
         从JDK 1.0开始java中每个对象都有一个隐式的锁.如果一个方法又synchronized关键字声明,那么对象的锁将保护整个方法,也就是说要调用这个方法,线程必须先获得对象的锁.
public synchronized void  method(){
         method body
}
这和刚才使用lock的情况等价.

如果还需要更多了解,请参见 Java核心技术-第二卷高级特性-多线程一章

关于.net上的解决方案请转到 .NET中多线程的同步资源访问

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值