解决原子性问题

原子性问题
原子性问题的源头是线程切换,如果能够禁用线程切换就可以解决原子性的问题,而操作系统做线程切换是依赖CPU中断的,所以禁用CPU发送中断就可以禁止线程切换。

在单核时代很容易实现:但是在多核场景下,同一时刻有可能有两个线程同时在执行,一个线程执行在CPU1上,一个线程执行在CPU2上,此时禁止CPU中断,只能保证COU上的线程连续执行,并不能保证同一时刻只有一个线程执行。

同一时刻只有一个线程执行,我们称之为互斥,如果我们能够保证对共享变量的修改是互斥的,那么就可以保证原子性。

Java提供的锁技术:synchronized

class X {
  // 修饰非静态方法
  synchronized void foo() {
    // 临界区
  }
  // 修饰静态方法
  synchronized static void bar() {
    // 临界区
  }
  // 修饰代码块
  Object obj = new Object();
  void baz() {
    synchronized(obj) {
      // 临界区
    }
  }
}  

Java一条隐士规则:
1.当修饰静态方法的时候,锁定的是当前类的Class对象
2.当修饰非静态方法时,锁定的时当前实例对象this

下面分析一下下面的场景:

class SafeCalc {
  long value = 0L;
  long get() {
    return value;
  }
  synchronized void addOne() {
    value += 1;
  }
}

synchronized修饰的的临界区时互斥的,同一个时刻只有一个线程能够执行临界区里的代码,而对一个对象的解锁操作 Happens-Before 于后续对这个对象的加锁操作,综合Happens-Before的传递性原则 可以得出 一个线程在临界区修改共享变量(在解锁之前)对于后续进入临界区的线程(该操作在解锁之后)是可见的,所以使用synchronized关键字可以解决两个线程修改共享变量的值和预期的不一样的问题。但只能get是没有synchronized修饰的,根据管程中锁的原则,解锁的操作总保证对于后续的解锁是可见的,而Get方法并没有加锁操作,所以没法保证可见性。

式列2:

class SafeCalc {
  static long value = 0L;
  synchronized long get() {
    return value;
  }
  synchronized static void addOne() {
    value += 1;
  }
}

这里使用两把锁保护同一资源,一个对象是this,一个对象是Class,由于临界区是两把锁保护的,因而这两个临界区没有互斥性,这样就会导致并发问题了。

式列3

class SafeCalc {
  long value = 0L;
  long get() {
    synchronized (new Object()) {
      return value;
    }
  }
  void addOne() {
    synchronized (new Object()) {
      value += 1;
    }
  }
}

加锁本质就是在锁对象的对象头中写入当前线程id,但是new object每次在内存中都是新对象,所以加锁无效。这样写会被Jvm逃逸分析的优化后synchronized代码会被优化掉,等于没加锁(而且这种new出来只在一个地方使用的对象,其它线程不能对它解锁,这个锁会被编译器优化掉)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值