>>如何解决原子性问题
源头:线程切换导致,操作线程切换是依赖CPU 中断的。禁止CPU 发生中断就能禁止线程切换。
互斥:同时只有一个线程执行,能够保证对共享变量的修改是互斥的。
>>锁机制
锁模型
切记:不要出现自家锁保护它家的资源。
Java 提供的锁技术 synchronized 关键字。可以用来修饰静态方法、静态代码块。
对synchronized 的深入理解可看这篇文章:https://blog.csdn.net/qq_32665329/article/details/84340332
class SafeCalc {
long value = 0L;
long get() {
return value;
}
synchronized void addOne() {
value += 1;
}
}
addOne() 被synchronized 修饰,所以即使在多线程情况下仍可以保证原子性。但是否能保证可见性呢?
答案是否定的。根据“管程中锁规则:对一个锁的解锁Happens-before 于后续对这个锁的加锁。”
即:前一个线程的解锁对后一个线程的加锁可见。这点的管程就是这里的synchronized。
注意:get() 方法并没有加锁操作,所以可见性是无法保证的。
改正:
class SafeCalc {
long value = 0L;
synchronized long get() {
return value;
}
synchronized void addOne() {
value += 1;
}
}
临界区get() 和addOne()
上图中的get() 方法和addOne() 方法是互斥的,线程进入临界区访问get() 方法还是addOne() 方法都需要先获得this 这把锁,也就是当前类的实例对象。
>>锁和受保护资源的关系
受保护资源和锁之间的关系是N:1 的关系,即一把锁可以保护多个资源。这就像实际生活中的“包场”。
class SafeCalc {
static long value = 0L;
synchronized long get() {
return value;
}
synchronized static void addOne() {
value += 1;
}
}
把value 改成了静态变量,addOne() 方法改成了静态方法。改动后的代码是两个锁保护一个资源。
两个锁是:this 和SafeCalc.class 。 受保护资源:value。因此两个临界区是没有互斥关系的。
临界区addOne() 对value 的修改 对 临界区get() 也没有可见性的保证,这就会导致并发问题。
两把锁保护一个资源
class SafeCalc {
long value = 0L;
long get() {
synchronized (new Object()) {
return value;
}
}
void addOne() {
synchronized (new Object()) {
value += 1;
}
}
}
这段代码无法解决可见性和原子性的问题。new Object() 每次在内存中都是新的对象,所以加锁无效。
加锁的本质:本质是在锁对象的对象头中写入当前线程的id
切记不能用多把锁保护同一个资源。