《Java并发编程实战》学习笔记 Day10

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6


原子性问题的源头是线程切换,而操作系统做线程切换是依赖CPU中断的,所以禁止CPU发生中断就可以禁止线程切换。单核CPU,这样是可行的,但是并不适合多核场景。

多核场景下,有可能同一时刻有多个线程执行,此时禁止CPU中断,只能保证CPU上的线程连续执行,不能保证同一时刻只有一个线程执行。同一时刻只有一个线程操作共享变量,我们称之为互斥,无论多核还是单核,只要保证了这一点就可以保证原子性。

简易锁模型

锁,在大多数时候都是我们互斥操作的首先,我们把一段需要互斥执行的代码称为临界区,线程在进入临界区之前,首先尝试加锁,如果成功,进入临界区,否则就等待,直到持有锁的线程解锁,持有锁的线程执行完临界区的代码后,执行解锁。

改进后的锁模型

首先,我们把临界区要保护的资源标注出来,如在临界区增加了一个元素,受保护资源R;其次,我们要保护资源R就得为它创建一把锁LR;最后,针对这把锁LR,我们还需要在进出临界区时添上加锁操作和解锁操作。

明确锁的范围,能够锁住的资源,可能容易出问题的地方:

  1. 锁住了错误的资源
  2. 锁的粒度太大,锁住的资源太多,导致性能太低。
Java语言提供的锁技术:synchronized

锁是通用的技术方案,Java提供的synchronized关键字,就是锁的一种实现,synchronized 关键字可以用来修饰方法,也可以用来修饰代码块。

Java 编译器会在 synchronized 修饰的方法或代码块前后自动加上加锁 lock() 和解锁 unlock(),这样做的好处就是加锁 lock() 和解锁 unlock() 一定是成对出现的。

synchronized里加锁解锁锁定的对象在哪里?

当synchronized修饰代码块的时候,锁定了一个obj对象

当synchronized修饰方法时:

  1. 当修饰静态方法的时候,锁定的是当前类的Class对象
  2. 当修饰非静态方法的时候,锁定的是当前实例对象this
用synchronized解决count+=1问题
class SafeCalc {
long value = 0L;
long get() {
return value;
}
synchronized void addOne() {
value += 1;
}
}

addOne()方法首先可以肯定,被synchronized修饰后,无论单核还是多核CPU,只有一个线程执行addOne()方法,所以保证了原子性。可见性问题?

管程中锁的规则:对一个锁的解锁Happens-Before于后续对这个锁的加锁。

管程,就是我们这里的 synchronized(至于为什么叫管程,我们后面介绍),我们知道 synchronized 修饰的临界区是互斥的,也就是说同一时刻只有一个线程执行临界区的代码;而所谓“对一个锁解锁 Happens-Before 后续对这个锁的加锁”,指的是前一个线程的解锁操作对后一个线程的加锁操作可见,综合 Happens-Before 的传递性原则,我们就能得出前一个线程在临界区修改的共享变量(该操作在解锁之前),对后续进入临界区(该操作在加锁之后)的线程是可见的。

执行完addOne()方法后,value的值对于get()方法的值是可见的吗?这个可见性是不能保证的。管程中锁的规则,是只保证后续对这个锁的加锁的可见性,get()方法没有加锁操作,所以可见性不能保证。解决的方案就是给get()方法也加synchronized。

get() 方法和 addOne() 方法都需要访问 value 这个受保护的资源,这个资源用 this 这把锁来保护。线程要进入临界区 get() 和 addOne(),必须先获得 this 这把锁,这样 get() 和 addOne() 也是互斥的。

锁和受保护资源的关系

受保护资源和锁之间的关联关系是N:1的关系。

对上边的例子改动,继续分析,把value改成静态变量,把addOne()方法改成静态方法,继续分析get() 方法和 addOne() 方法是否存在并发问题?

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

改动后,受保护的资源是静态变量value,用两个锁保护一个资源,两个锁分别是this和SafeCalc.class。由于临界区是用两个锁保护的,所以set的过程中可以get,临界区就不存在互斥性关系,临界区addOne()对value的修改临界区get()也没有可见性,就导致了并发问题。


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java