竞争条件和关键区

竞争条件是一个可能发生在关键区域的特殊条件。关键区是由多线程执行的一个代码区,多线程一系列的并发执行关键区导致不同结果。 多线程执行执行关键区的结果的不同和线程执行的顺序有关,关键区就被认为包含竞争条件。竞争条件这个词来源于线程竞争关键区的暗喻,竞争的结果会影响执行关键区的结果 这个听起来可能有点复杂,所以我会在竞争条件和关键区上有一个详细的介绍。

关键区

在一个程序中运行多个线程程序自身不会引起任何问题。当多线程访问相同的资源的时候就会引起问题的发生。例如一样的内存(变量、数组或者对象)、系统(数据库、web服务等等)或者文件 事实上,多个线程写入这些资源的时候问题就会出现,只要资源没有发生改变,多线程读取相同的资源是没有任何问题的。 下边是一个java关键区代码的例子,如果多个线程并发的执行就可能会出问题
  public class Counter {

     protected long count = 0;

     public void add(long value){
         this.count = this.count + value;
     }
  }


想象一下两个线程A和B,在一个Counter类的实例上执行add方法,没有什么方法能够知道什么时候操作系统在两个线程之间切换,add方法中的代码在JVM中并不是原子执行的,而是由一系列的小的步骤组成的,和这个很像
1、从主存中读取count到寄存器中
2、把值增加到寄存器中
3、把寄存器写回到内存中
观察下载线程A和B中混合执行,会发生什么
       this.count = 0;

   A:  Reads this.count into a register (0)
   B:  Reads this.count into a register (0)
   B:  Adds value 2 to register
   B:  Writes register value (2) back to memory. this.count now equals 2
   A:  Adds value 3 to register
   A:  Writes register value (3) back to memory. this.count now equals 3


两个线程想要增加值2和值3给这个counter,因此在两个线程执行完成之后,它的值应该是5。因为两个线程的执行是交错进行的,因此结果也是不同的。 在上边例子中列出的执行顺序,两个线程从内存中读取的值为0,然后他们单独增肌它们的值2和3,把结果写回到内存中。除了5,this.count的值将会是最后写入的值,在上边的例子的结果是因为它是线程A的结果,也可能是线程B的结果。

关键区域中的竞争条件

上边例子中的add方法包含关键区,当多线程执行这个方法的时候,竞争条件就会发生。更通常的情况是,两个线程竞争同样的资源,访问资源的顺序很重要,这就被称作是竞争条件。导致竞争条件的代码区就叫做关键区。

阻止竞争条件

为了阻止竞争条件的发生,你就必须确保关键区的原子执行,也就意味着一次只能有一个线程执行它,在这个线程没有离开关键区之前其他线程不能执行。 在关键区上进行适当的线程同步可以避免竞争条件的发生。线程同步可以用Java的synchronize代码块来实现。线程同步也可用其他的同步构造如locks或者原子变量来实现

关键区的输出

对于小的关键区把整个的关键区作为一个同步代码块或许可以工作。但是对于更大的关键区,将他们划分成更小的关键区或许会更好,这样多线程就可以在不同的关键区上执行。这会减少共享资源的竞争,就会提高整个关键区的吞吐
下边这个一个简单的java代码的例子
public class TwoSums {
    
    private int sum1 = 0;
    private int sum2 = 0;
    
    public void add(int val1, int val2){
        synchronized(this){
            this.sum1 += val1;   
            this.sum2 += val2;
        }
    }
}


注意add方法是在两个不同的成员变量上增加值。为了阻止sum竞争条件的发生加上的java的同步块,用这种方法的实现同时只能有一个现在在方法上执行。但是,因为两个sum变量彼此之间是独立的,你可以把他们分开为两个独立的同步代码块,就像这样:
public class TwoSums {
    
    private int sum1 = 0;
    private int sum2 = 0;
    
    public void add(int val1, int val2){
        synchronized(this){
            this.sum1 += val1;   
        }
        synchronized(this){
            this.sum2 += val2;
        }
    }
}


现在可以有两个线程同时在add方法上执行了。一个线程在第一个同步代码块,另一个线程在另一个同步代码块中,这个例子非常简单,当然,在实际的生活中将有共享资源的关键区分开是非常复杂的,需要更多的分析。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值