1)回顾一下什么是原子性?
-
一个或多个操作在CPU执行的过程中不被中断的特性,称为原子性。
2)原子性问题的源头是什么?
-
线程切换
3)原子性问题到底该如何解决呢?
-
既然原子性问题的产生源头是线程切换,而线程切换依赖CPU中断的,所以禁止CPU发生中断就能禁止线程切换
4)为什么说单核的时候好解决原子性,而多核不好解决?
-
人多了不好管理
5)32 位 CPU 上执行 long 型变量的写操作是怎样的步骤?
-
被分成了两次写操作(写高 32 位和写低 32 位)
6)互斥怎么理解?
-
同一时刻只有一个线程执行
7)保证原子性的关键是什么?
-
保证对共享变量的修改是互斥的
8)互斥的最简单方案就是加锁,那么简易锁模型是怎样的?
9)简易锁模型有哪些不足?
-
我们不能清晰的知道锁的是什么?我们保护的又是什么?
10)从哪些方面去改进我们的简易锁模型?
-
在现实世界里,锁和锁要保护的资源是有对应关系的,比如你用你家的锁保护你家的东西,我用我家的锁保护我家的东西。
-
在并发编程世界里,锁和资源也应该有这个关系。
11)改进后的锁模型应该是怎样的?
12)改进后的锁有哪些注意要点?
-
锁 LR 和受保护资源之间,我特地用一条线做了关联,这个关联关系非常重要。很多并发 Bug 的出现都是因为把它忽略了,然后就出现了类似锁自家门来保护他家资产的事情
13)Java 语言提供的锁技术是什么?
-
synchronized
14)synchronized可以锁哪些对象?
-
方法
-
代码块
15)写一段代码看看?
class X {
// 修饰非静态方法
synchronized void foo() {
// 临界区
}
// 修饰静态方法
synchronized static void bar() {
// 临界区
}
// 修饰代码块
Object obj = new Object();
void baz() {
synchronized(obj) {
// 临界区
}
}
}
16) 为什么代码中没有看见加锁---解锁这个顺序?,不符合上面的模型啊!
-
java悄悄滴帮我们加上了。
17)java自动帮我们加锁解锁对我们有什么好处?
-
加锁 lock() 和解锁 unlock() 一定是成对出现的,避免我们忘记解锁导致bug
18)上面的代码中synchronized在代码块中锁的对象是obj。那它在修饰方法的时候锁的对象是什么?
-
修饰静态方法:锁的是当前类的 Class 对象
// 上面的例子相当于
class X {
// 修饰静态方法
synchronized(X.class) static void bar() {
// 临界区
}
} -
修饰非静态方法:锁的是当前实例对象 this
class X {
// 修饰非静态方法
synchronized(this) void foo() {
// 临界区
}
}
19)怎样用synchronized 来解决 count+=1 问题?
class SafeCalc {
long value = 0L;
long get() {
return value;
}
synchronized void addOne() {
value += 1;
}
}
20)上面的代码真的解决了并发问题吗?
-
addOne() 方法,被 synchronized 修饰后。无论是单核 CPU 还是多核 CPU,只有一个线程能够执行 addOne() 方法,所以一定能保证原子操作
20.1)虽然synchronized解决了addOne() 原子性问题,那是否有可见性问题呢?
-
前一个线程在临界区修改的共享变量(该操作在解锁之前),对后续进入临界区(该操作在加锁之后)的线程是可见的。
-
我们上面说的前后线程可见针对的对象是addOne() 方法。
现在我要求的是get() 方法去读addOne() 方法,他两没啥关系,所以get()方法是看不见我们addOne()方法的变化的
20.2)如何解决get()方法看不见addOne()方法的问题呢?
-
get() 方法也 synchronized 一下
class SafeCalc {
long value = 0L;
synchronized long get() {
return value;
}
synchronized void addOne() {
value += 1;
}
} -
他两锁的对象都是this,所以根据管程中的几大规则,就保证了我们get能够看见addOne的变化。
20.3)上面代码锁模型图是怎么样的?
你去看球赛,一张票一个座位。座位就是受保护资源,门票就是锁,检票人员就是synchronized。
21)锁和受保护资源的关系是什么?
-
一把锁可以锁多个资源,一个资源只能有一把锁。
-
一张包场票包下所有座位,一个座位只能卖一张票,否则打架。
22)分析下面改动的代码是否存在并发问题?
class SafeCalc {
static long value = 0L;
synchronized long get() {
return value;
}
synchronized static void addOne() {
value += 1;
}
}
静态方法的锁是类,而普通方法的锁是this对象,这是两把不同的锁,所以存在并发问题。
23)下面的代码用 synchronized 修饰代码块来尝试解决并发问题,你觉得这个使用方式正确吗?有哪些问题呢?能解决可见性和原子性问题吗?
class SafeCalc {
long value = 0L;
long get() {
synchronized (new Object()) {
return value;
}
}
void addOne() {
synchronized (new Object()) {
value += 1;
}
}
}
-
1)两个代码块锁对象是不同的对象。
-
2)经过JVM逃逸分析的优化后,这个sync代码直接会被优化掉,所以在运行时该代码块是无锁的
24)加锁的本质是什么?
-
在锁对象的对象头中写入当前线程id。