鱼友3172
Java后端
·2025-11-16
学习笔记 如何解决原子性问题呢?都知道了,原子性的源头是线程切换,那么我们禁用线程切换不就可以了?而操作系统做线程切换是依赖于CPU中断的,所以我们禁用CPU中断不就可以了?在一个单核时代确实是可以,一个CPU禁用线程切换,就意味着一个线程执行任务,期间不会有任何干扰,像count++这种操作,读取count->修改count->将count写入内存,在这一系列的操作中,不会受到干扰。 但是在多核时代,禁止CPU中断,就无法解决我们的问题了。即使我们对每个CPU都进行禁止CPU中断,也只是保证了,在一个CPU上的这个线程是连续运行的,但是对一个操作的原子性还是无法保证,依然会受到干扰。例如:CPU-1上的线程A执行count++操作,但是在执行到count写入内存之前,CPU-2的线程B也执行了count++操作了,它去内存读取了count,那么此时线程A的count++操作就受到了干扰,因为它还没完整的执行完毕,这就无法保证原子性了。 所以说,“一个操作在同一时刻只有一个线程执行”这个条件非常重要,我们也可以称之为互斥。而提到互斥,我们第一时间想到的应该是锁吧。直接看代码 class SafeCalc { long value = 0L; synchronized long get() { return value; } synchronized void addOne() { value += 1; } } synchronized修饰方法addOne,锁住的是当前的实例对象this,多个线程使用同一个实例对象,调用addOne方法时,可以确保只有一个线程执行addOne方法,保证了原子性。 但是,我们要注意,还有一个get方法,这个方法用来读取value的值,它可没有加锁,那么value的值对于get方法来说是可见的吗?get方法能不能拿到最新的value值呢?答案是不能的,按照我们学习过的happens-before规则,显然是无法保证get方法拿到的value值是可见的。解决方法也很简单,给get方法也使用this锁给它锁住就行。 class SafeCalc { long value = 0L; synchronized long get() { return value; } synchronized void addOne() { value += 1; } } 这样,当多个线程使用同一个对象去执行get或addOne方法的时候,只有一个线程会先获取到this锁访问方法,其他线程访问这两个方法的时候,去获取this锁时,因为this锁只有一个且已经被持有了,所以就需要进行等待。访问get方法的时候,addOne方法就无法被访问,形成了互斥,同时,根据我们的happens-before规则三,一个线程释放锁对于另一个线程获取同一个锁是可见的。 在这个例子里,我们保护的资源是value,我们可以清晰的看到,get方法和addOne方法里的资源都是value,我们都是使用this锁这一把锁来对value这个资源来进行保护的。这里就要说一个点,受保护的资源和锁之间的关联是很重要的,受保护的资源和锁的对应关系一般是n:1,也就是说,多份资源可以用一把锁来保护,但是反过来就不太行了。在现实中,我们当然可以用多个锁去保护一份资源,但在这里,会有问题,请看例子。 class SafeCalc { static long value = 0L; synchronized long get() { return value; } synchronized static void addOne() { value += 1; } } get方法是普通方法,而addOne是静态方法,synchronized修饰普通方法,使用的锁是当前实例对象this的锁,而synchronized修饰静态方法,使用的锁是当前类的Class对象的锁,即SafeCalc.Class锁,他们两个是不一样的锁,是两个锁。 当一个线程A去执行get方法时,会获取this锁,然后访问临界区,此时如果有线程B去执行addOne方法的话,会去获取SafeCalc.Class锁,如果拿到了SafeCalc.Class锁,就会访问addOne,get方法和addOne方法不是互斥的了,同时,也不适用happens-before规则三,无法保证可见性。
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP