synchronized 关键字在多线程并发编程中一直是元老级角色的存在, 是学习并发编程中必须面对的坎, 也是走向 Java 高级开发的必经之路.
一, synchronized 性质
synchronized 是 Java 提供的内置锁机制, 有如下两种特性:
互斥性: 即在同一时间最多只有一个线程能持有这种锁. 当线程 1 尝试去获取一个由线程 2 持有的锁时, 线程 1 必须等待或者阻塞, 知道线程 2 释放这个锁. 如果线程 2 永远不释放锁, 那么线程 1 将永远等待下去.
可重入性: 即某个线程可以获取一个已经由自己持有的锁.
二, synchronized 用法
Java 中的每个对象都可以作为锁. 根据锁对象的不同, synchronized 的用法可以分为以下两种:
对象锁: 包括方法锁 (默认锁对象为 this 当前实例对象) 和同步代码块锁(自己制定锁对象)
类锁: 指的是 synchronized 修饰静态的方法或指定锁为 Class 对象.
三, 多线程访问同步方法的 7 种情况
本部分针对面试中常考的 7 中情况进行代码实战和原理解释.
1. 两个线程同时访问一个对象的同步方法
- /**
- * 两个线程同时访问一个对象的同步方法
- */
- public class Demo1 implements Runnable {
- static Demo1 instance = new Demo1();
- @Override
- public void run() {
- fun();
- }
- public synchronized void fun() {
- System.out.println(Thread.currentThread().getName() + "开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "运行结束");
- }
- public static void main(String[] args) {
- Thread thread1 = new Thread(instance);
- Thread thread2 = new Thread(instance);
- thread1.start();
- thread2.start();
- while (thread1.isAlive() || thread2.isAlive()) {
- }
- System.out.println("finished");
- }
- }
结果: 两个线程顺序执行.
解释: thread1 和 thread2 共用一把锁 instance; 同一时刻只能有一个线程获取锁; thread1 先启动, 先获得到锁, 先运行, 此时 thread2 只能等待. 当 thread1 释放锁之后, thread2 获取到锁, 进行执行.
2. 两个线程访问的是两个对象的同步方法
- public class Demo2 implements Runnable{
- static Demo2 instance1 = new Demo2();
- static Demo2 instance2 = new Demo2();
- @Override
- public void run() {
- fun();
- }
- public synchronized void fun() {
- System.out.println(Thread.currentThread().getName() + "开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "运行结束");
- }
- public static void main(String[] args) {
- Thread thread1 = new Thread(instance1);
- Thread thread2 = new Thread(instance2);
- thread1.start();
- thread2.start();
- while (thread1.isAlive() || thread2.isAlive()) {
- }
- System.out.println("finished");
- }
- }
结果: 两个线程并行执行.
解释: thread1 使用的锁对象是 instance1,thread2 使用的锁对象是 instance2, 两个对象使用的锁对象不是同一个, 所以线程之间互不影响, 是并行执行的.
3. 两个线程访问的是 synchronized 的静态方法
- public class Demo3 implements Runnable{
- static Demo3 instance1 = new Demo3();
- static Demo3 instance2 = new Demo3();
- @Override
- public void run() {
- fun();
- }
- public static synchronized void fun() {
- System.out.println(Thread.currentThread().getName() + "开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "运行结束");
- }
- public static void main(String[] args) {
- Thread thread1 = new Thread(instance1);
- Thread thread2 = new Thread(instance2);
- thread1.start();
- thread2.start();
- while (thread1.isAlive() || thread2.isAlive()) {
- }
- System.out.println("finished");
- }
- }
结果: 两个线程顺序执行.
解释: 虽然两个线程使用了两个不同的 instance 实例, 但是只要方法是静态的, 对应的锁对象是同一把锁, 需要先后获取到锁进行执行.
4. 同时访问同步方法与非同步方法
- public class Demo4 implements Runnable {
- static Demo4 instance = new Demo4();
- @Override
- public void run() {
- if (Thread.currentThread().getName().equals("Thread-0")){
- fun1();
- }else{
- fun2();
- }
- }
- public synchronized void fun1() {
- System.out.println(Thread.currentThread().getName() + "开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "fun1 运行结束");
- }
- public void fun2() {
- System.out.println(Thread.currentThread().getName() + "fun2 开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "运行结束");
- }
- public static void main(String[] args) {
- Thread thread1 = new Thread(instance);
- Thread thread2 = new Thread(instance);
- thread1.start();
- thread2.start();
- while (thread1.isAlive() || thread2.isAlive()) {
- }
- System.out.println("finished");
- }
- }
结果: 两个线程并行执行.
解释: synchronize 的关键字只对 fun1 起作用, 不会对其他方法造成影响. 也就是说同步方法不会对非同步方法造成影响, 两个方法并行执行.
- ### 5. 访问同一个对象的不同的普通同步方法
- public class Demo5 implements Runnable {
- static Demo5 instance = new Demo5();
- @Override
- public void run() {
- if (Thread.currentThread().getName().equals("Thread-0")){
- fun1();
- }else{
- fun2();
- }
- }
- public synchronized void fun1() {
- System.out.println(Thread.currentThread().getName() + "开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "fun1 运行结束");
- }
- public synchronized void fun2() {
- System.out.println(Thread.currentThread().getName() + "fun2 开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "运行结束");
- }
- public static void main(String[] args) {
- Thread thread1 = new Thread(instance);
- Thread thread2 = new Thread(instance);
- thread1.start();
- thread2.start();
- while (thread1.isAlive() || thread2.isAlive()) {
- }
- System.out.println("finished");
- }
- }
结果: 顺序执行.
解释: 两个方法共用了 instance 对象锁, 两个方法无法同时运行, 只能先后运行.
6. 同时访问静态 synchronized 和非静态的 synchronized 方法
- public class Demo6 implements Runnable{
- static Demo6 instance = new Demo6();
- @Override
- public void run() {
- if (Thread.currentThread().getName().equals("Thread-0")){
- fun1();
- }else{
- fun2();
- }
- }
- public static synchronized void fun1() {
- System.out.println(Thread.currentThread().getName() + "开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "fun1 运行结束");
- }
- public synchronized void fun2() {
- System.out.println(Thread.currentThread().getName() + "fun2 开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "运行结束");
- }
- public static void main(String[] args) {
- Thread thread1 = new Thread(instance);
- Thread thread2 = new Thread(instance);
- thread1.start();
- thread2.start();
- while (thread1.isAlive() || thread2.isAlive()) {
- }
- System.out.println("finished");
- }
- }
结果: 两个线程并行执行
解释: 有 static 关键字, 锁的是类本身; 没有 static 关键字, 锁的是对象实例; 锁不是同一把锁, 两个锁之间是没有冲突的; 所以两个线程可以并行执行.
7. 方法抛异常后, 会释放锁
- public class Demo7 implements Runnable{
- static Demo7 instance = new Demo7();
- @Override
- public void run() {
- if (Thread.currentThread().getName().equals("Thread-0")){
- fun1();
- }else{
- fun2();
- }
- }
- public synchronized void fun1() {
- System.out.println(Thread.currentThread().getName() + "开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- throw new RuntimeException();
- //System.out.println(Thread.currentThread().getName() + "fun1 运行结束");
- }
- public synchronized void fun2() {
- System.out.println(Thread.currentThread().getName() + "fun2 开始运行");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "运行结束");
- }
- public static void main(String[] args) {
- Thread thread1 = new Thread(instance);
- Thread thread2 = new Thread(instance);
- thread1.start();
- thread2.start();
- while (thread1.isAlive() || thread2.isAlive()) {
- }
- System.out.println("finished");
- }
- }
结果: thread1 运行时遇到异常, 并未运行结束, thread2 开始运行, 并运行至结束.
解释: 方法抛出异常后, JVM 自动释放锁.
8. 上述 7 种情况总结
3 点核心思想:
一把锁只能同时被一个线程获取, 没有拿到锁的线程必须等待.
每个实例都对应有自己的一把锁, 不同实例之间互不影响; 例外: 锁对象是. class 以及 synchronized 修饰的是 static 方法的时候, 所有对象共用同一把锁.
无论是方法正常运行完毕或者方法抛出异常, 都会释放锁.
四, synchronized 和 ReentrantLock 比较
虽然 ReentrantLock 是更加高级的锁机制, 但是 synchronized 依然存在着如下的优点:
synchronized 作为内置锁为更多的开发人员所熟悉, 代码简洁;
synchronized 较 ReentrantLock 更加安全, ReentrantLock 如果忘记在 finally 中释放锁, 虽然代码表面上运行正常, 但实际上已经留下了隐患
synchronized 在线程转储中能给出在哪些调用帧中获得了哪些琐, 并能够检测和识别发生死锁的线程.
五, 总结
synchronized 关键字是 Java 提供的一种互斥的, 可重入的内置锁机制.
其有两种用法: 对象锁和类锁.
虽然 synchronized 与高级锁相比有着不够灵活, 效率低等不足, 但也有自身的优势: 安全, 依然是并发编程领域不得不学习的重要知识点.
来源: https://www.cnblogs.com/sgh1023/p/11405176.html