【并发】并发锁机制-深入理解synchronized(一)
synchronized 基础篇(使用)
一、Java共享内存模型带来的线程安全问题
1. 代码示例
2. 运行结果
3. 问题分析
4. 临界区(Critical Section)
5. 竞态条件(Race Condition)
二、synchronized 的使用
1. 同步普通(实例)方法
2. 同步静态方法
3. 同步this实例对象
4. 同步类对象
5. 同步对象实例
解决之前的共享问题
遗留问题(高级篇种解释)
下一节——synchronized 高级篇(底层原理)
这一篇文章主要介绍synchronized的使用和其底层原理!我将会由浅入深带大家学习synchronized!
思考: 两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?
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);}
}
以上的结果可能是正数、负数、零(但是为0,基本是不可能出现的!)


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指令顺序执行,(不交错)那肯定是没有问题的。
但是,在多线程下,可能会交错运行!
一个程序运行多个线程本身是没有问题的!问题出在多个线程访问共享资源
多个线程(只)读共享资源其实也没有问题
在多个线程对共享资源读写操作时发生指令交错,就会出现问题!!!
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区,其共享资源为临界资源
例如,我们上述的代码中的如下部分:
//临界资源
private static int counter = 0;public static void increment() { //临界区counter++;
}public static void decrement() {//临界区counter--;
}
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
为了避免临界区的竞态条件发生,有多种手段可以达到目的:
需要注意的是,在Java中同步和互斥的场景都是使用synchronized实现的!但它们是有区别的!
synchronized 同步块是 Java 提供的一种原子性内置锁,Java 中的每个对象都可以把它当作一个同步锁来使用,这些 Java 内置的使用者看不到的锁被称为内置锁,也叫作监视器锁。
大致是有 5种用法!

public synchronized void method() {...
}
public static synchronized void method() {...
}
synchronized(this) {...
}
synchronized(synchronizedDemo.class){...
}
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种方式在性能上面是否有差别?