synchronized深度解析

一、 背景

高并发问题向来是Java程序员进阶的重点,也是面试的难点,而其中synchronized是不得不趟过的坑。所以专门整理记录了下synchronized的理解和笔记,从synchronized使用方法到底层原理源码,还对常见面试题和更深层扩展方面的思考,做出了讲解。

二、 Synchronized简介

2.1 synchronized的作用

  • 能够保证在【同一时刻】最多只有【一个】线程执行该段代码,以达到保证并发安全的效果

2.2 synchronized的地位

  • synchronized是java的关键字,也是最基本的互斥同步手段

2.3 不加并发手段(如synchronized)的后果

/**
 * DisappearRequest1
 *
 * @Description: 消失的未读数
 * @since unknown, 2019-09-14
 */
public class DisappearRequest1 implements Runnable{
    static DisappearRequest1 instance = new DisappearRequest1();
    static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }

    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            count++;
        }
    }
}
//输出结果
166810
  • 原因
count++,它看上去只是一个操作,实际上包含了3个动作:
(1)读取count
(2)将count加1
(3)将count的值写入到内存中
  • 存在场景:上诉的场景也称为线程不安全
线程1读取count为9,执行了(2)加1,这个时候线程2过来读取了count(也是为9),
然后线程1、2都执行完,最终count就是为10(实际上应该需要为11)

三、 Synchronized的两种用法(对象锁和类锁)

3.1 Synchronized的2种用法介绍

  • 对于多个线程,争抢的是同一把锁的话,就是串行阻塞的。如果是不同对象(锁不是同一把),则是并行的

3.1.1、对象锁

  • 包括【方法锁】(synchronized修饰普通方法,锁对象默认为this,即当前实例对象)
  • 【同步代码块锁】(手动指定锁对象)

3.1.2、类锁

  • 指synchronized修饰【静态】的方法
  • 或指定锁为【Class对象】

3.2 对象锁

3.2.1 对象锁的形式1:同步代码块(手动指定锁对象)

注意:以下操作都是针对同一个对象实例的,如果是2个对象实例,synchronized是相互不影响的

  • 锁this
/**
 * SynchronizedObjectCodeBlock2
 * 对象锁-方法锁this
 * @Description: 对象锁示例1,代码块形式-方法锁this
 * @since unknown, 2019-09-14
 */
public class SynchronizedObjectCodeBlock2 implements Runnable {
    static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();

    @Override
    public void run() {
    //这里this是instance,因为2个线程都是使用同一个静态实例instance,所以可以锁
    //对于不同对象实例,this是不同的,synchronized不生效
        synchronized (this) {  
            System.out.println("我是对象锁的代码块形式,我叫 " + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()) {

        }
        System.out.println("finished");
    }
}
//输出结果
我是对象锁的代码块形式,我叫 Thread-0
Thread-0运行结束 //延时3s后输出
我是对象锁的代码块形式,我叫 Thread-1 //Thread-0执行完了,释放了锁,Thread-1获得锁可以执行了
Thread-1运行结束
finished
  • 手动锁对象(多个对象锁)
/**
 * SynchronizedObjectCodeBlock2
 * lock1和lock2是并行的
 * @Description: 对象锁示例1,代码块形式-手动锁对象
 * @since unknown, 2019-09-14
 */
public class SynchronizedObjectCodeBlock2 implements Runnable {
    static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
    Object lock1 = new Object();
    Object lock2 = new Object();

    @Override
    public void run() {
        synchronized (lock1) {
            System.out.println("我是lock1,我叫 " + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " lock1运行结束");
        }

        synchronized (lock2) {
            System.out.println("我是lock2,我叫 " + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " lock2运行结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()) {

        }
        System.out.println("finished");
    }
}
//输出结果
我是lock1,我叫 Thread-0
Thread-0 lock1运行结束 //延时3s
我是lock1,我叫 Thread-1 //Thread-0释放了lock1,Thread-1可以获取了
我是lock2,我叫 Thread-0
Thread-1 lock1运行结束
Thread-0 lock2运行结束
我是lock2,我叫 Thread-1
Thread-1 lock2运行结束
finished

3.2.2 对象锁的形式2:普通方法锁

  • 对方法加了synchronized,2个线程会排队等待,串行完成调用
public class SynchronizedObjectMethod3 implements Runnable {
    static SynchronizedObjectMethod3 instance = new SynchronizedObjectMethod3();

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while(t1.isAlive() || t2.isAlive()) {

        }
        System.out.println("finished");
    }

    @Override
    public void run() {
        method();
    }
    public synchronized void method() {
        System.out.println("我是对象锁的方法修饰符形式,我叫 " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
}
//运行结果
我是对象锁的方法修饰符形式,我叫 Thread-0
Thread-0运行结束 //延时3s后输出
我是对象锁的方法修饰符形式,我叫 Thread-1
Thread-1运行结束
finished
  • 使用2个对象的场景,synchronized相互不影响
public class SynchronizedObjectMethod3 implements Runnable {
    static SynchronizedObjectMethod3 instance = new SynchronizedObjectMethod3();

    public static void main(String[] args) {
        Thread t1 = new Thread(new SynchronizedObjectMethod3());
        Thread t2 = new Thread(new SynchronizedObjectMethod3());
        t1.start();
        t2.start();
        while(t1.isAlive() || t2.isAlive()) {

        }
        System.out.println("finished");
    }

    @Override
    public void run() {
        method();
    }
    public synchronized void method() {
        System.out.println("我是对象锁的方法修饰符形式,我叫 " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
//输出结果
我是对象锁的方法修饰符形式,我叫 Thread-1
我是对象锁的方法修饰符形式,我叫 Thread-0 //同时输出,2个对象的场景是并行的
Thread-1运行结束
Thread-0运行结束
finished

3.3 类锁

3.3.1 类锁的概念

  • 概念(重要):Java类可能有很多个对象,但只有1个Class对象
  • 本质:所谓的类锁,不过是Class对象的锁而已
  • 用法和效果:类锁只能在同一时刻被一个对象所拥有
(1)形式1:synchronized加在static方法上
(2)形式2:synchronized(*.class)代码块

3.3.2 类锁的形式1:静态方法锁

  • synchronized加在static方法上
  • 由于静态方法是共享的,所以即使是2个对象实例instance,但对于method还是唯一串行阻塞的。因此,对于需要使用synchronized的,如果有多个对象,但需要都串行阻塞的,可以考虑使用对方法加static synchronized,使其成为一个静态方法锁
/**
 * SynchronizedClassStatic4
 *
 * @Description: 类锁的第一种形式,static形式
 * @since unknown, 2019-09-13
 */
public class SynchronizedClassStatic4 implements Runnable{
    static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
    static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();
    @Override
    public void run() {
        method();
    }

    public static synchronized void method() {
        System.out.println("我是类锁的第一种形式:static形式,我叫 " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()) {

        }
        System.out.println("finished");
    }
}
//输出结果
我是类锁的第一种形式:static形式,我叫 Thread-0
Thread-0运行结束 //延时3s后输出
我是类锁的第一种形式:static形式,我叫 Thread-1
Thread-1运行结束 //延时3s后输出
finished

3.3.3 类锁的形式2:synchronized(*.class)代码块

  • 对要执行的代码块添加:synchronized (SynchronizedClassClass5.class) {},则使用该代码块的方法为加锁串行阻塞
/**
 * SynchronizedClassClass5
 *
 * @Description: 类锁的第二种形式,*.class形式
 * @since unknown, 2019-09-14
 */
public class SynchronizedClassClass5 implements Runnable {
    static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();
    static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();

    @Override
    public void run() {
        method();
    }

    private void method() {
        synchronized (SynchronizedClassClass5.class) {
            System.out.println("我是类锁的第二种形式:synchronized(*.class)。我叫 " + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()) {

        }
        System.out.println("finished");
    }
}
//输出结果
我是类锁的第一种形式:static形式,我叫 Thread-0
Thread-0运行结束 //延时3s后输出
我是类锁的第一种形式:static形式,我叫 Thread-1
Thread-1运行结束 //延时3s后输出
finished

3.4 使用学到的对象锁和类锁处理i++的问题

public class SynchronizedDisappearRequest6 implements Runnable {
    static SynchronizedDisappearRequest6 instance = new SynchronizedDisappearRequest6();
    static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }

//    //对象锁-对普通方法加synchronized(同个对象实例)
//    @Override
//    public synchronized void run() {
//        for (int j = 0; j < 100000; j++) {
//            i++;
//        }
//    }

//    //对象锁-同步代码块(手动指定锁对象)
//    @Override
//    public void run() {
//        synchronized (this) {
//            for (int j = 0; j < 100000; j++) {
//                i++;
//            }
//        }
//    }

//        //类锁-*.class
//    @Override
//    public void run() {
//        synchronized (SynchronizedDisappearRequest6.class) {
//            for (int j = 0; j < 100000; j++) {
//                i++;
//            }
//        }
//    }

    //类锁-静态方法锁
    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            i++;
        }
    }

    //run方法override不支持扩展为static,不处理
}
//输出结果
200000

四、 多线程访问同步方法的7种具体情况

4.1 具体情况

  • 1、两个线程同时访问【一个对象】的同步方法
串行:锁是同一把,所以是串行阻塞的
  • 2、两个线程访问的是【两个对象】的同步方法
并行:不同对象的同步方法,锁对象不是同一个,锁不是同一把,所以是相互不影响,并行执行
  • 3、两个线程访问的是synchronized的【静态】方法
串行:静态方法是同一个,锁是同一把,都是.class,所以是串行阻塞的
  • 4、同时访问【同步】方法与【非同步】方法
并行:只会影响同步方法,非同步方法不受到影响
  • 5、访问同一个对象的【不同的】普通同步方法
串行:同一个对象的2个synchronized方法,使用的都是同一把锁(this对象),所以是串行阻塞的
  • 6、同时访问【静态】synchronized和【非静态】synchronized方法
并行:
【静态】synchronized是类锁,加锁的对象的.class
【非静态】synchronized是对象锁,加锁的是某个对象
2个方法加锁的对象不同,因此不受影响,并行执行
  • 7、方法抛【异常】后,会【释放锁】
/**
 * SynchronizedException9
 *
 * @Description: 方法抛出异常后,会释放锁。展示不抛出异常前和抛出异常后的对比;一旦抛出了异常,第二个线程会立刻进入同步方法,意味着锁已经释放。
 * @since unknown, 2019-09-15
 */
public class SynchronizedException9 implements Runnable {
    static SynchronizedException9 instance = new SynchronizedException9();

    @Override
    public void run() {
        if ("Thread-0".equals(Thread.currentThread().getName())) {
            method1();
        } else {
            method2();
        }
    }

    public synchronized void method1() {
        System.out.println("我是静态加锁的方法1,我叫" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //抛出不检查的RuntimeException,JVM会释放锁
        throw new RuntimeException();
        //System.out.println(Thread.currentThread().getName() + "运行结束");
    }

    public synchronized void method2() {
        System.out.println("我是非静态加锁的方法2,我叫" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()) {

        }
        System.out.println("finished");
    }
}
//输出结果
我是静态加锁的方法1,我叫Thread-0
我是非静态加锁的方法2,我叫Thread-1 //延时3s后输出,由于Thread-0抛出异常,JVM释放了锁,Thread-1可以获得锁
Exception in thread "Thread-0" java.lang.RuntimeException
	at ConcurrenceFolder.mooc.SynchronizedException9.method1(SynchronizedException9.java:29)
	at ConcurrenceFolder.mooc.SynchronizedException9.run(SynchronizedException9.java:16)
	at java.lang.Thread.run(Thread.java:748)
Thread-1运行结束
finished

4.2 总结

  • 1、一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况)
  • 2、每个实例都对应有自己的一把锁,不同实例之间互不影响 例外:锁对象是*.class或者synchronized修饰的static方法时,所有对象共用同一把类锁(对应第2、3、4、6种情况)
  • 3、无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)
synchronized方法调用非synchronized,不是线程安全的:因为非synchronized可以被多个线程调用,
虽然当前synchronized方法是安全的,但是调用的普通方法可以被其他所有线程随时调用,更新了数据(例如方法内部更新了静态变量),
因此不是线程安全的

五、 Synchronized的性质

  • 可重入
  • 不可中断

5.1 可重入

5.1.1 可重入概念

  • 什么是可重入:指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。
可重入:一个线程拿到了锁,这个线程可以再次使用该锁对其他方法,说明该锁是可以重入的
不可重入:一个线程拿到锁了,如果需要再次使用该锁,必须先释放该锁才能再次获取
  • 好处:避免死锁,提升封装性。
1、可避免死锁,锁方法1在内部访问锁方法2,用的是同一把锁
如果不具备可重入性,那么就会造成  拿锁-未释放锁、尝试拿锁但拿不到,
因此造成死锁

5.1.2 粒度

  • 粒度:synchronized的粒度是线程而非调用(用3种情况来说明和pthread的区别)pthread粒度是调用。

5.1.2.1 粒度有3种情况

  • 情况1:证明同一个方法是可重入的
/**
 * SynchronizedRecursion10
 *
 * @Description: 可重入粒度测试:递归调用本方法
 * @since unknown, 2019-09-15
 */
public class SynchronizedRecursion10 {
    int a = 0;

    public static void main(String[] args) {
        SynchronizedRecursion10 instance = new SynchronizedRecursion10();
        instance.method1();
    }

    private synchronized void method1() {
        System.out.println("这是method1,a = " + a);
        if (a == 0) {
            a++;
            method1();
        }

    }
}
//输出
这是method1,a = 0
这是method1,a = 1
  • 情况2:证明可重入不要求是同一个方法
/**
 * SynchronizedOtherMethod11
 *
 * @Description: 可重入粒度测试:调用类的其他方法
 * @since unknown, 2019-09-15
 */
public class SynchronizedOtherMethod11 {
    public synchronized void method1() {
        System.out.println("我是method1");
        method2();
    }

    private synchronized void method2() {
        System.out.println("我是method2");
    }

    public static void main(String[] args) {
        SynchronizedOtherMethod11 instance = new SynchronizedOtherMethod11();
        instance.method1();
    }
}
//输出
我是method1
我是method2
  • 情况3:证明可重入不要求是同一个类中的
/**
 * SynchronizedSuperClass12
 *
 * @Description: 可重入粒度测试,调用父类的方法
 * @since unknown, 2019-09-15
 */
public class SynchronizedSuperClass12 {
    public synchronized void doSomeThing() {
        System.out.println("我是父类方法");
    }
}

class TestClass extends SynchronizedSuperClass12 {
    @Override
    public synchronized void doSomeThing() {
        System.out.println("我是子类方法");
        super.doSomeThing();
    }

    public static void main(String[] args) {
        TestClass testClass = new TestClass();
        testClass.doSomeThing();
    }
}
//输出
我是子类方法
我是父类方法

5.2 不可中断

  • 1、线程A拿到锁,不释放的话,别人永远拿不到锁,永远等待;
  • 2、Lock锁会有一些比较灵活的功能,按时间等
(1)如果我觉得我等待时间过长,有权中断现在已经获取到锁的线程的执行
(2)如果我我觉得我等待的时间太长了,不想等待了,也可以退出

六、深入原理

  • 加锁和释放锁的原理:现象、时机、深入JVM看字节码
  • 可重入原理:加锁次数计数器
  • 保证可见性的原理:内存模型

6.1 加锁和释放锁的原理

6.1.1 现象:每一个类的实例对应着一把锁,而每一个synchronized方法都必须首先获得调用该方法的类的实例的锁,才能执行。否则线程就会阻塞,而方法一旦执行,就会独占这把锁,直到该方法返回,或者抛出异常,才将锁释放。释放之后,其他被阻塞的线程才能获得这把锁,重新进入可执行的状态。

6.1.2 获取和释放锁的时机:内置锁

6.1.3 等价代码

/**
 * SynchronizedToLock13
 *
 * @Description: synchronized和Lock等价
 * @since unknown, 2019-09-16
 */
public class SynchronizedToLock13 {
    Lock lock = new ReentrantLock();
    public synchronized void method1() {
        System.out.println("我是Synchronized形式的锁");
    }

    public void method2() {
        lock.lock();
        try {
            System.out.println("我是lock形式的锁");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        SynchronizedToLock13 instance = new SynchronizedToLock13();
        instance.method1();
        instance.method2();
    }
}
//输出结果
我是Synchronized形式的锁
我是lock形式的锁

6.2 深入JVM看字节码:反编译、monitor指令

6.2.1 概况:

  • synchronize用的锁是java对象头里的一个字段(每一个对象都有一个对象头,对象头可以存储很多信息,其中有一部分就是用来存储synchronize关键字的锁)

6.2.2 细节

  • 当线程访问代码块的时候必须要得到这把锁,退出整个代码块或者抛出异常的时候必须会释放锁,在JVM规范中对于jVM实现原理,已经有了说明,它的进入锁和释放锁是基于moniter对象来实现同步方法和同步代码块的,Monitor对象主要是两个指令,一个是Monitorenter(插入到同步代码块开始的位置),和Monitorexit(退出,插入到方法结束的时候和退出的时候),jvm的规范要求每一个enter必须要要有exit和他对应,但是可能一个monitorenter对应多个Monitorexit,退出的时机包括方法结束和抛出异常。每一个对象都有一个Monitor和他关联,并且moniter被持有后,就会处于锁定状态,当线程执行到Monitorenter指令时,会尝试获取这个对象对应的Monitor的所有权,也是尝试获取对象的锁。

6.2.3 反编译字节码

javac Decompilation14.java
javap 把class文件解读成字节码文件,java  -verbose:加入verbose参数,事无巨细的所有的信息都打印出来
javap -verbose Decompilation14.class
——————
Classfile /E:/myworkplace/IDEA/TestGit/src/ConcurrenceFolder/mooc/Decompilation14.class
  Last modified 2019-9-16; size 502 bytes
  MD5 checksum 79c1024a1dd2f23b69bf7586c51e9073
  Compiled from "Decompilation14.java"
public class ConcurrenceFolder.mooc.Decompilation14
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #2.#20         // java/lang/Object."<init>":()V
   #2 = Class              #21            // java/lang/Object
   #3 = Fieldref           #4.#22         // ConcurrenceFolder/mooc/Decompilation14.object:Ljava/lang/Object;
   #4 = Class              #23            // ConcurrenceFolder/mooc/Decompilation14
   #5 = Utf8               object
   #6 = Utf8               Ljava/lang/Object;
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               insert
  #12 = Utf8               (Ljava/lang/Thread;)V
  #13 = Utf8               StackMapTable
  #14 = Class              #23            // ConcurrenceFolder/mooc/Decompilation14
  #15 = Class              #24            // java/lang/Thread
  #16 = Class              #21            // java/lang/Object
  #17 = Class              #25            // java/lang/Throwable
  #18 = Utf8               SourceFile
  #19 = Utf8               Decompilation14.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Utf8               java/lang/Object
  #22 = NameAndType        #5:#6          // object:Ljava/lang/Object;
  #23 = Utf8               ConcurrenceFolder/mooc/Decompilation14
  #24 = Utf8               java/lang/Thread
  #25 = Utf8               java/lang/Throwable
{
  public ConcurrenceFolder.mooc.Decompilation14();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #2                  // class java/lang/Object
         8: dup
         9: invokespecial #1                  // Method java/lang/Object."<init>":()V
        12: putfield      #3                  // Field object:Ljava/lang/Object;
        15: return
      LineNumberTable:
        line 10: 0
        line 11: 4

  public void insert(java.lang.Thread);
    descriptor: (Ljava/lang/Thread;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=2
         0: aload_0
         1: getfield      #3                  // Field object:Ljava/lang/Object;
         4: dup
         5: astore_2
         6: monitorenter  //这里有一个monitorenter入口
         7: aload_2
         8: monitorexit  //对应的有monitorexit,一个入口有多个monitorexit,可能是正常结束或者抛异常
         9: goto          17
        12: astore_3
        13: aload_2
        14: monitorexit
        15: aload_3
        16: athrow
        17: return
      Exception table:
         from    to  target type
             7     9    12   any
            12    15    12   any
      LineNumberTable:
        line 14: 0
        line 16: 7
        line 17: 17
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class ConcurrenceFolder/mooc/Decompilation14, class java/lang/Thread, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}
SourceFile: "Decompilation14.java"

——————

6.2.4 详细解读Monitorenter和Monitorexit指令

  • Monitorenter和Monitorexit在执行的时候会使对象的锁计数加1或者减1,和操作系统中的PV操作(多线程对临界资源的访问)很像,每一个对象都和一个Monitor相关联,一个monitor的locksware只能被一个线程在同一时间获得,一个线程在尝试获得与这个对象关联的Monitor所有权的时候(Monitorenter),只会发生以下三种情况之一
(1)Monnitor计数器为0,意味着目前还还没有被获得,这个线程就会立刻获得,然后把计数器加1,之后别人再想进来就会看到信号,
知道它已经被其他线程所持有,加1,意味着当前线程是这个Monitor持有者。(成功获得锁)

(2)当前对象拿到锁的所有权,又重入了,就会导致计数器累加,会变成2,3……随着重入的次数而增加(已经有了这把锁再次重入的情况)

(3)Monitor已经被其他线程所持有了,当前线程再次获取就会得到现在无法获取的信号,就会进入阻塞状态,
直到Monitor的计数器变为0,才会再次尝试获取这个锁
  • Monitorexit
作用,释放Monitor(Monitor可以理解为锁)的所有权,前提是已经拥有了锁的所有权,释放的过程就是将Monitor的计数器减1,
减完之后变成0就意味着当前线程不在拥有对Monitor的所有权,就是解锁,如果减完之后不是0,意味着刚才是可重入进来的,
所以还继续持有这把锁,最终减到0之后,不仅意味着释放锁了,还意味着刚才被阻塞的线程,会再次尝试获取对该把锁的所有权

6.3 可重入原理

  • 1、JVM负责跟踪对象被加锁的次数;
  • 2、有个monitor计数器,线程第一次给对象加锁的时候,计数变为1.每当这个相同的线程在此对象上再次获得锁时,计数会递增。
  • 3、任务结束离开,则会执行monitorexit,计数递减,直到完全释放

6.4 可见性原理

  • synchronize可见性的实现:一旦一个方法或者代码块被Synchronized修饰之后,那么锁执行完毕之后,被锁住的对象所做的任何修改,都要在释放锁之前,从线程内存写回到主内存中,这样可以避免线程内存和主内存内容不一致,因为它在进入代码块得到锁之后,被锁定的对象的数据也是直接从主内存中读取出来的,而它在释放的时候会把修改好的内容写回到主内存中,所以从主内存中读取到的数据一定是最新的

七、 Synchronized的缺陷

  • 效率低:锁的释放情况少、试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程。
  • 不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的。
  • 无法知道是否成功获取到锁。

八、 常见面试题

  • 使用注意点:锁对象不能为空,作用域不宜过大,避免死锁。
  • 如何选择Lock和synchronized关键字?
1)如果可以的话,建议都不使用。可以使用java.util.concurrent包中的Automic类、countDown等类
2)优先使用现成工具,如果没有就优先使用synchronized关键字,好处是写尽量少的代码就能实现功能。
如果需要灵活的加解锁机制,则使用Lock接口
  • 多线程访问同步方法的各种具体情况
  • 1、一个线程释放锁,JVM如何决定下一个获取该锁的线程:
线程释放锁的时候,等待中的线程会随机获得锁,没有顺序,获得机制和jvm版本等有关
有可能是新来的获取到,有可能是老的处于阻塞状态的线程获取到,
随机,不公平的。
  • 2、synchronized使得同时只有一个线程可以执行,性能较差,有什么办法可以提升性能?
缩小加锁范围,需要加锁的地方才加锁
  • 3、想要更灵活地控制锁的获取和释放(现在释放锁的时机都被规定死了),怎么办?
使用Lock,自定义等待时间
  • 4、什么是锁的升级、降级?什么是JVM里的偏斜锁、轻量级锁、重量级锁
所谓锁的升级、降级,就是JVM优化synchronized运行的机制,当JVM检测到不同的竞争状况时,会自动切换到适合的锁实现,
这种切换就是锁的升级、降级。

十、 全文总结

  • 一句话介绍synchronized:JVM会自动通过使用monitor来加锁和解锁,保证了同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入和不可中断的性质。 
  • 【参考资料】《Java高并发之魂:synchronized深度解析》https://www.imooc.com/learn/1086
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Venlenter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值