13 - synchronized 同步互斥锁

1. synchronized 使用

1.1 线程安全问题

  并发编程中,当多个线程同时访问同一个资源的时候,就会存在线程安全问题。

  由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际期望的结果相违背或者直接导致程序出错。下面的代码,每执行一次 add10K() 方法,都会循环 10000 次 count+=1 操作。在 calc() 方法中我们创建了两个线程,每个线程调用一次 add10K() 方法,我们来想一想执行 calc() 方法得到的结果应该是多少呢?

public class Test {
   
  private long count = 0;
  private void add10K() {
   
    int idx = 0;
    while(idx++ < 10000) {
   
      count += 1;
    }
  }
  public static long calc() {
   
    final Test test = new Test();
    // 创建两个线程,执行 add() 操作
    Thread th1 = new Thread(()->{
   
      test.add10K();
    });
    Thread th2 = new Thread(()->{
   
      test.add10K();
    });
    // 启动两个线程
    th1.start();
    th2.start();
    // 等待两个线程执行结束
    th1.join();
    th2.join();
    return count;
  }
}

  如上面程序,直觉告诉我们应该是20000,因为在单线程里调用两次add1OK()方法,但实际的结果是10000-20000之间的随机数。为什么呢?

  java并发程序都是基于多线程的,自然也就会涉及到任务切换,任务切换的时机大多数都是在时间片结束的时候,我们现在基本都使用高级语言编程,高级语言里的一条语句往往需要多条CPU指令完成,如count += 1,至少需要三条CPU指令。

  • 指令1:首先,需要把变量count从内存加载到CPU寄存器;
  • 指令2:之后,在寄存器中执行+1操作;
  • 指令3:最后,将结果写入内存(缓存机制导致可能写入的是CPU缓存而不是内存)。

  操作系统做任务切换,可以发生在任何一条CPU指令执行完,注意是CPU指令,而不是高级语言里的一条语句。对于上面三条指令来说,我们假设count=0,如果线程A在指令1执行完后做线程切换,线程A和线程B按照下图的序列执行,那么两个线程都执行了count +=1的操作,得到的结果不是我们期望的2,而是1。

基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。
通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值