【并发】并发锁机制-深入理解synchronized(一)
synchronized 基础篇(使用)
一、Java共享内存模型带来的线程安全问题
1. 代码示例
2. 运行结果
3. 问题分析
4. 临界区(Critical Section)
5. 竞态条件(Race Condition)
二、synchronized 的使用
1. 同步普通(实例)方法
2. 同步静态方法
3. 同步this实例对象
4. 同步类对象
5. 同步对象实例
解决之前的共享问题
遗留问题(高级篇种解释)
下一节——synchronized 高级篇(底层原理)
【并发】并发锁机制-深入理解synchronized(一)
这一篇文章主要介绍synchronized的使用和其底层原理!我将会由浅入深带大家学习synchronized!
synchronized 基础篇(使用)
一、Java共享内存模型带来的线程安全问题
思考: 两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?
1. 代码示例
public class SyncDemo {
private static int counter = 0;
public static void increment() {
counter++;
}
public static void decrement() {
counter--;
}
public static void main(String[] args) throws InterruptedException {
// 线程一
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
increment();
}
}, "t1");
// 线程二
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
decrement();
}
}, "t2");
t1.start();
t2.start();
// main线程 要等到t1、t2线程 运行结束后才会终止
t1.join();
t2.join();
System.out.println("counter = " + counter);
}
}
2. 运行结果
以上的结果可能是正数、负数、零(但是为0,基本是不可能出现的!)
3. 问题分析
Java 中对静态变量的自增,自减并不是原子操作。
我们可以查看 i++和 i--(i 为静态变量)的 JVM 字节码指令
i++的JVM 字节码指令
getstatic i // 获取静态变量i的值
iconst_1 // 将int常量1压入操作数栈
iadd // 自增
i--的JVM 字节码指令
getstatic i // 获取静态变量i的值
iconst_1 // 将int常量1压入操作数栈
isub // 自减
如果单线程情况下,这些JVM指令顺序执行,(不交错)那肯定是没有问题的。
但是,在多线程下,可能会交错运行!
4. 临界区(Critical Section)
一个程序运行多个线程本身是没有问题的!问题出在多个线程访问共享资源
多个线程(只)读共享资源其实也没有问题
在多个线程对共享资源读写操作时发生指令交错,就会出现问题!!!
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区,其共享资源为临界资源
例如,我们上述的代码中的如下部分:
//临界资源
private static int counter = 0;
public static void increment() { //临界区
counter++;
}
public static void decrement() {//临界区
counter--;
}
5. 竞态条件(Race Condition)
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
为了避免临界区的竞态条件发生,有多种手段可以达到目的:
- 阻塞式的解决方案:synchronized(本章节重点),Lock
- 非阻塞式的解决方案:原子变量(Atomic原子类,CAS)
需要注意的是,在Java中同步和互斥的场景都是使用synchronized实现的!但它们是有区别的!
- 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
- 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点
二、synchronized 的使用
synchronized 同步块是 Java 提供的一种原子性内置锁,Java 中的每个对象都可以把它当作一个同步锁来使用,这些 Java 内置的使用者看不到的锁被称为内置锁,也叫作监视器锁。
大致是有 5种用法!
1. 同步普通(实例)方法
public synchronized void method() {
...
}
2. 同步静态方法
public static synchronized void method() {
...
}
3. 同步this实例对象
synchronized(this) {
...
}
4. 同步类对象
synchronized(synchronizedDemo.class){
...
}
5. 同步对象实例
String lock = "";
synchronized(lock){
...
}
解决之前的共享问题
方法一:
private static int counter = 0;
public static synchronized void increment() {
counter++;
}
public static synchronized void decrement() {
counter--;
}
方法二:
private static int counter = 0;
private static String lock = "";
public static void increment() {
synchronized (lock) {
counter++;
}
}
public static void decrement() {
synchronized (lock) {
counter--;
}
}
synchronized 实际是用对象锁保证了临界区内代码的原子性
遗留问题(高级篇种解释)
同步实例对象和同步类对象有什么区别?
这5种方式在性能上面是否有差别?