原子性问题的源头是 线程切换
Q:如果禁用 CPU 线程切换是不是就解决这个问题了?
A:单核 CPU 可行,但到了多核 CPU 的时候,有可能是不同的核在解决同一个变量,即使不切换线程,也有问题。
所以,解决原子性的要害是「同一时刻只有一个线程解决该变量,也被称为互斥」。
如何做到呢?用「锁」。
一、锁模型
一)繁难锁模型
个别看到的锁模型长上面这样。
但对于这个模型,会有几个疑难:
- 锁的是什么?
- 临界区的这一堆代码相干的都被锁了?
- 爱护的又是什么?
二)改良后的锁模型
用上面这个模型来解释就解答了下面几个问题:
- 要爱护的是临界区中的资源 R
- 因而要为 R 创立一个对应的锁 LR
- 须要解决资源 R 的时候先加锁,解决完之后解锁
- 一个资源必须和锁对应,不能用 A 锁去锁 B 资源
二、Java 提供的锁技术
Java 提供了多种技术,这里仅谈及 Synchronized
。
Synchronized 关键字
Java 语言提供的 synchronized 关键字,就是锁的一种实现。synchronized 关键字能够用来润饰办法,也能够用来润饰代码块。
class X {
// 润饰非静态方法
synchronized void foo() {// 临界区}
// 润饰静态方法
synchronized static void bar() {// 临界区}
// 润饰代码块
Object obj = new Object();void baz() {synchronized(obj) {// 临界区}
}
}
Q:synchronized
没看到 lock 和 unlock?
A:在编译的时候会做转换,synchronized
起始的中央加锁,完结的中央解锁。
Q:那么 synchronized
锁的是什么呢?
A:当润饰静态方法时,锁定的是以后类的 Class 对象,在下面的例子中就是 Class X;
当润饰非静态方法时,锁定的是以后实例对象 this。
当润饰代码块时,括号中写的是啥就锁啥。
(可能不精确)
Class 对象是用来保留类信息的,能够了解为元数据?
实例对象则是每一个 new 进去的非凡的个体
Synchronized 实例
public class SynchronizedTT {
private int value = 0;
//public void printValue() {public synchronized void printValue() {System.out.println(this.value);
}
public synchronized void addValue() throws InterruptedException {Thread.sleep(1000);
this.value += 1;
}
}
// 开两个线程,一个先调用 addValue(),另一个后调用 printValue()
💡思考:如果 printValue()
不增加 synchronized
关键字,会造成什么样的后果?
三、锁和受爱护资源的关系
要点:
- 一把锁能够爱护多个资源
- 然而一个资源只能用一把锁爱护
- 受爱护资源和锁之间的关联关系是 N:1 的关系
💡思考:如果用多把锁锁同一个资源会呈现什么状况?
上面例子:
synchronized 是不同的锁,就和没锁一样。
public class SynchronizedTT {
private static int value = 0;
public synchronized void printValue() {System.out.println(value);
}
public synchronized static void addValue() throws InterruptedException {Thread.sleep(1000);
value += 1;
}
}