- 首先来看一下多线乱序访问导致的线程安全问题
static int i = 0;
public static void main(String[] args) {
new Thread(()->{
i=+1; // 如果这里是 i++ 的话 不会出现安全问题,因为i++ 是原子操作
System.out.println("第一个 i = " + i);
}).start();
new Thread(()->{
i=+1;// i=i+1; 复合操作,先读 ,在写
System.out.println("第2 个 i = " + i);
}).start();
}
输出结果.png输出结果:
- 从结果中可以看出,在多线程下,出现了数据错误的情况。
下面我们来分析一下多线程发生安全问题的原因。
- JVM 运行时 内存管理(JVM 运行时区),jvm 将静态变量或者成员变量是存法在 方法区或者 堆内存中的,这两块区域都是 多线线程共享区域。
-
JMM 内存模型
jmm.png
这里的共享变量就是 x。首先 线程 A 会 从 主内存中读取 x = 0 ,然后会拷贝一个线程本地本地变量 x= 0,然后 进行 加1 (x= x+1),最后会i 的值写回主内存中。也就上 经常说的复合操作(读,写)。
如果某一时刻 A 和 B 读取到 主内存中的 x 都相同 如 x = 0,然后各自在自己的 线程中计算 加1 (x = x+1),此时x = 1 ,A和B 同时将x写回 主内存,x= 1。其实我们在代码中对x 进行了 2 次加1 ,x 正确的值 是 2 ,但是现在是1 。这就导致了线程安全问题。(这里引申出一些概念 :可见性,原子性,一致性,后面将jvm 底层实现同步的时候解释)。
- synchronized 同步
为了解决多线程安全问题,java 引入synchronized 同步锁。下面先来介绍一下 如何使用。
- 方法(对象级别)上的同步
//共享变量
private int i = 0;
// 同步方法,锁的范围在对象范围
public synchronized void add1() {
i = i+1;
}
// 这两种写法是等级的
public void add2() {
synchronized (this) {
i = i+1;
}
}
public static void main(String[] args) {
HelloController helloController = new HelloController();
// HelloController helloController2 = new HelloController();
// 下面两个线程都是 使用 的 helloController 对象,因为add1 方法 的锁的范围在 对象级别,只有使用同一个对象才能保证同步。
new Thread(()->{
helloController.add1();
System.out.println("i = "+helloController.i);
}).start();
new Thread(()->{
helloController.add1();
System.out.println("i = "+helloController.i);
}).start();
}
输出结果 (2).png输出:
- 类级别上的同步
private static int j = 0;
// 静态方法上的同步,锁的范围在 类级别上
public static synchronized void add3() {
j = j+1;
}
// 这两个是等价的
public void add4() {
synchronized (HelloController.class) {
j = j+1;
}
}
public static void main(String[] args) {
new Thread(()->{
HelloController.add3();
System.out.println("j = "+ j);
}).start();
new Thread(()->{
HelloController.add3();
System.out.println("j = "+ j);
}).start();
}
synchronized 基本使用就到这里。
网友评论