一文解决synchronized

参考:Java高并发之魂:synchronized深度解析(_悟空_)
链接:https://pan.baidu.com/s/18P7U4mIUC9wUWiHhDWi2xg
提取码:bpij
代码:https://github.com/ouyangxizhu/concurrency_demo.git

文章目录

  • 简述
  • 一、synchronized作用
  • 二、问题引入
    • 运行结果
    • 原因
  • 三、synchronized的两个用法
    • 1. 对象锁
      • 1) 方法锁(synchronized修饰非静态方法)
      • 2) 同步代码块锁
    • 2. 类锁
      • 1)syncronized修饰静态方法。
      • 2)指定锁为Class对象
  • 阅读到这里,你可以解决文章最开始的“问题引入”提到的问题了(i++的问题)
  • 四、多线程访问同步方法的7中情况(面试常考)
    • 1. 两个线程同时访问一个对象的同步方法
    • 2. 两个线程访问两个对象的同步方法
    • 3. 两个线程访问synchronized的静态方法
    • 4. 同时访问同步方法和非同步方法
    • 5. 访问同一个对象的不同的普通同步方法
    • 6. 同时访问静态的synchronized方法和非静态的synchronized方法
    • 7. 方法抛出异常后会释放锁
  • 五、总结
  • 六、补充
  • 七、性质
    • 1. 可重入
      • 好处
        • 1)避免死锁
        • 2)提升封装性
      • 粒度
        • 证明同一个方法是可重入的
        • 证明可重入不要求是同一个方法
        • 证明可重入不要求是同一个类中的
    • 2. 不可中断
  • 八、原理
    • 现象
    • 等价代码
  • 九、深入JVM看字节码monitor指令
    • 当一个线程想获得锁时(monitorenter指令)有三种情况
    • 当一个线程执行monitorexit指令
  • 十、可见性
  • 十一、缺陷
    • 1. 效率低
    • 2. 不够灵活(读写锁更灵活)
    • 3. 无法知道是否成功获得锁
  • 十二、synchronized使用注意事项
    • 1. 锁对象不能为空
    • 2. 作用域不宜过大
    • 3. 避免死锁
  • 十三、如何选择Lock和synchronized
    • 1. 尽量都不使用,尽量使用JUC包下的类(使用CAS实现)
    • 2. 尽量使用synchronized,因为可以少写代码。
    • 3. 使用Lock的特性时才用Lock
  • 十四、思考题
    • 1. 多个线程等待同一个synchronized锁的时候,JVM如何选择下一个获取锁的线程。(什么算法?一直等待的?还是刚刚来的线程)
    • 2. synchronized使得同时只有一个线程可以执行,性能较差,有什么办法可以提升性能?
    • 3. 我想更加灵活的控制获取和释放(现在释放锁的时机已经被规定死了)
    • 4. 什么是锁的升级和降级,什么是JVM的偏向锁,轻量级锁,重量级锁
  • 十五、总结
    • 一句话概括synchronized

简述

synchronized是java的关键字,是java语言原生支持的,是最基本的互斥同步手段。

一、synchronized作用

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

二、问题引入

两个线程同时执行a++,最后结果会比预计的。(synchronized_demo->DisappearRequest1)

package synchronized_demo;
/**
 * Created by ouyangxizhu on 2019/5/15.
 * 消失的请求
 */
public class DisappearRequest1 implements Runnable{
    static DisappearRequest1 instance = new DisappearRequest1();
    static int i = 0;
    public static void main(String[] args){
        try {
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
            t1.join();
            t2.join();//join方法保证该线程的方法执行完之后才能执行下面的代码。
            System.out.println(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        for(int j = 0; j < 100000; j++)
            i++;
    }
}

运行结果

DisappearRequest1运行结果
这个结果小于200000(不论运行多少次)。

原因

因为i++这个操作不是原子操作。具体的解释如下:
i++其实是三个操作

  1. 线程读取i的值。int temp = i;
  2. 将该值+1。temp = temp + 1;(其实这的表述有问题,temp = temp + 1也不是原子操作其实等同于i++。只是用这个表示将该值+1,即假设是原子操作+1)。
  3. 将该值写入到内存。i = temp;
    具体解释是这样的:
  4. 假设t1线程读取i的值为0。
  5. 这时t2线程读取i的值也为0;(因为t1线程还没修改或者修改后还没写入到内存)
  6. t1和t2线程分别执行+1操作,再写入内存。
    执行结果就是少加了一次1。这种情况导致最后的结果小于200000。

三、synchronized的两个用法

在本小结的阅读中,读者要是有问题或者想到其他形式(但是不知道运行结果),读者可以自己尝试或者文章后面会有全部形式的例子

1. 对象锁

1) 方法锁(synchronized修饰非静态方法)

默认锁对象为this,即当前实例对象。(synchronized_demo->SynchronizedObjectMethod2)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 对象锁实例,普通方法锁
 */
public class SynchronizedObjectMethod2 implements Runnable{
    static SynchronizedObjectMethod2 instance = new SynchronizedObjectMethod2();
    @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() + "运行结束");
    }

    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");
    }
}

运行结果
一文解决synchronized_第1张图片
结果为顺序执行。

2) 同步代码块锁

自己指定锁对象。(synchronized_demo->SynchronizedObjectCodeBlock3)
package synchronized_demo;

/**

  • Created by ouyangxizhu on 2019/5/15.

  • 对象锁 代码块形式
    */
    public class SynchronizedObjectCodeBlock3 implements Runnable{
    static SynchronizedObjectCodeBlock3 instance = new SynchronizedObjectCodeBlock3();
    @Override
    public void run() {
    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”);
    }
    }
    运行结果
    一文解决synchronized_第2张图片
    结果为顺序执行。

2. 类锁

1)syncronized修饰静态方法。

(synchronized_demo->SynchronizedClassStatic4)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 类锁 synchronized修饰静态方法形式
 */
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("我是类锁的synchronized修饰静态方法形式,我叫" + 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");
    }
}

运行结果
一文解决synchronized_第3张图片
顺序执行。

2)指定锁为Class对象

java类可以有很多对象,但是只有一个类(Class)对象,即synchronized(*.class)代码块形式。其实是Class对象的锁而已,只不过只有一个而已。(synchronized_demo->SynchronizedClassClass5)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 类锁 synchronized(*.class)形式
 */
public class SynchronizedClassClass5 implements Runnable {
    static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();
    static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();

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

    }
    public synchronized 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");
    }
}

运行结果
一文解决synchronized_第4张图片
顺序执行。

阅读到这里,你可以解决文章最开始的“问题引入”提到的问题了(i++的问题)

四、多线程访问同步方法的7中情况(面试常考)

1. 两个线程同时访问一个对象的同步方法

(synchronized_demo->SynchronizedStyle1)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 两个线程同时访问一个对象的同步方法
 */
public class SynchronizedStyle1 implements Runnable{
    static SynchronizedStyle1 instance = new SynchronizedStyle1();
    @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() + "运行结束");
    }

    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");
    }
}

运行结果:
一文解决synchronized_第5张图片
顺序执行

2. 两个线程访问两个对象的同步方法

(synchronized_demo->SynchronizedStyle2)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 两个线程访问两个对象的同步方法
 */
public class SynchronizedStyle2 implements Runnable{
    static SynchronizedStyle2 instance1 = new SynchronizedStyle2();
    static SynchronizedStyle2 instance2 = new SynchronizedStyle2();
    @Override
    public void run() {
        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(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive());//空循环
        System.out.println("finished");
    }
}

运行结果:
一文解决synchronized_第6张图片
或者
一文解决synchronized_第7张图片
: 这两个结果表示,线程可以先运行Thread-0也可以先运行Thread-1。

3. 两个线程访问synchronized的静态方法

(synchronized_demo->SynchronizedStyle3)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 *  两个线程访问synchronized的静态方法
 */
public class SynchronizedStyle3 implements Runnable {
    static SynchronizedStyle3 instance1 = new SynchronizedStyle3();
    static SynchronizedStyle3 instance2 = new SynchronizedStyle3();

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

    }
    public static synchronized void method(){
        System.out.println("我是两个线程访问synchronized的静态方法形式,我叫" + 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");
    }
}

一文解决synchronized_第8张图片

4. 同时访问同步方法和非同步方法

(synchronized_demo->SynchronizedStyle4)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 同时访问同步方法和非同步方法
 */
public class SynchronizedStyle4 implements Runnable {
    static SynchronizedStyle4 instance = new SynchronizedStyle4();


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

    }
    public synchronized void method1(){
        System.out.println("我是加锁形式,我叫" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    public void method2(){
        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");
    }
}

一文解决synchronized_第9张图片

5. 访问同一个对象的不同的普通同步方法

(synchronized_demo->SynchronizedStyle5)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 访问同一个对象的不同的普通同步方法
 */
public class SynchronizedStyle5 implements Runnable {
    static SynchronizedStyle5 instance = new SynchronizedStyle5();


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

    }
    public synchronized void method1(){
        System.out.println("我是加锁形式,是method1,我叫" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    public synchronized void method2(){
        System.out.println("我是加锁形式,是method2,我叫" + 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");
    }
}

一文解决synchronized_第10张图片
或者
一文解决synchronized_第11张图片

6. 同时访问静态的synchronized方法和非静态的synchronized方法

(synchronized_demo->SynchronizedStyle6)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 同时访问静态的synchronized方法和非静态的synchronized方法
 */
public class SynchronizedStyle6 implements Runnable {
    static SynchronizedStyle6 instance = new SynchronizedStyle6();


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

    }
    public static synchronized void method1(){
        System.out.println("我是静态加锁method1,我叫" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    public synchronized void method2(){
        System.out.println("我是非静态加锁method2,我叫" + 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");
    }
}

一文解决synchronized_第12张图片
一个是类锁,一个是对象锁,锁不一样,所以有这种结果。

7. 方法抛出异常后会释放锁

(synchronized_demo->SynchronizedStyle7)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 方法抛出异常后会释放锁
 * 一旦第一个线程抛出异常,第二个线程会立刻获得锁
 */
public class SynchronizedStyle7 implements Runnable {
    static SynchronizedStyle7 instance = new SynchronizedStyle7();


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

    }
    public synchronized void method1(){
        System.out.println("我是非静态加锁method1,我叫" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        throw new RuntimeException();

    }
    public synchronized void method2(){
        System.out.println("我是非静态加锁method2,我叫" + 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");
    }
}

一文解决synchronized_第13张图片

五、总结

  1. 一个锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1,5的情况)。
  2. 每个实例都对应自己的一把锁,不同实例之间互不影响;例外:当锁对象是*.class或者synchronized修饰静态方法时所有对象获得的锁是同一把锁(类锁,该类只有一个类对象)。(对应2,3,4,6)
  3. 方法无论是正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)。

六、补充

在一个同步方法中调用非同步方法时不是线程安全的(非同步方法不是线程安全,可以同时访问)。

七、性质

1. 可重入

同一线程的外层函数获得锁之后,内层函数可以直接获得该锁。

好处

1)避免死锁

比如:假设一个类里面有两个同步方法,第一个方法里面有第二个同步方法的调用。如果一个实例对象的线程想执行第一个同步方法,当执行第一个方法里面的第二个方法的调用的时候会出现死锁。(执行第一个同步方法时获得了该实例对象的锁,当在该方法中调用第二个方法时,必须获得该实例对象锁,但是该锁已经被该实例对象持有。如果不可重入,会永远等待,产生死锁,第二个方法不会被执行)。

2)提升封装性

避免一次次解锁加锁,简化编程。

粒度

线程而非调用。

证明同一个方法是可重入的

(synchronized_demo->SynchronizedRecursion1)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 粒度证明    证明同一个方法是可重入的(递归调用本身)
 */
public class SynchronizedRecursion1 {
    int a = 0;
    public synchronized void method1() {
        System.out.println("我是method1,a = " + a);
        if (a == 0) {
            a++;
            method1();
        }
    }

    public static void main(String[] args) {
        SynchronizedRecursion1 synchronizedRecursion1 = new SynchronizedRecursion1();
        synchronizedRecursion1.method1();
        System.out.println("finished");
    }
}

一文解决synchronized_第14张图片

证明可重入不要求是同一个方法

(synchronized_demo->SynchronizedRecursion2)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 粒度证明    证明可重入不要求是同一个方法   调用类内另外的方法
 */
public class SynchronizedRecursion2 {

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

    public static void main(String[] args) {
        SynchronizedRecursion2 synchronizedRecursion1 = new SynchronizedRecursion2();
        synchronizedRecursion1.method1();
        System.out.println("finished");
    }
}

一文解决synchronized_第15张图片

证明可重入不要求是同一个类中的

(synchronized_demo->SynchronizedRecursion3)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 粒度证明    证明可重入不要求是同一个类中的   调用父类的方法
 */
public class SynchronizedRecursion3 {

    public synchronized void doSomething() {
        System.out.println("我是父类的方法" );

    }

}
class TestClass extends SynchronizedRecursion3{
    public synchronized void doSomething() {
        System.out.println("我是子类的方法" );
        super.doSomething();
    }
    public static void main(String[] args) {
        TestClass s = new TestClass();
        s.doSomething();
        System.out.println("finished");
    }
}

一文解决synchronized_第16张图片
这个例子我觉得写的不好。自己写了一个
(synchronized_demo->SynchronizedRecursion3_1)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 */
public class SynchronizedRecursion3_1 {
    static TestSyn t = new TestSyn();
    public static void method(){
        synchronized(t.getClass()){
            System.out.println("我是SynchronizedRecursion3_1的method方法");
        }
    }
}
class TestSyn{
    public static void main(String[] args){
        method();
    }
    public synchronized static void method(){
        System.out.println("我是TestSyn的method方法");
        SynchronizedRecursion3_1.method();
    }
    
}

一文解决synchronized_第17张图片

2. 不可中断

一旦这个锁已经被别的线程获得了,如果本线程还想获得,只能选择等待或者阻塞,直到别的线程释放这个锁。如果别的线程永远不释放,本线程永远拿不到。
Lock类有中断的能力,第一点,可以中断现在已经获得锁的线程的执行。第二点如果等待时间太长,可以退出。

八、原理

现象

当前线程要执行同步代码或者同步代码块时,当前线程必须获得指定对象锁,当前线程获得之后别的线程(想要执行该方法或者代码块)都要等待,直到当前线程执行完同步方法或者同步代码块,或者发生异常退出才会释放锁。

等价代码

(synchronized_demo->SynchcronizedToLock)

package synchronized_demo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by ouyangxizhu on 2019/5/15.
 */
public class SynchcronizedToLock {
    Lock lock = new ReentrantLock();
    public synchronized void method1(){
        System.out.println("我是synchronized形式的锁");
    }
    public void method2(){
        try {
            lock.lock();
            System.out.println("我是Lock形式的锁");
        } finally {
            lock.unlock();
        }

    }
    public static void main(String[] args){
        SynchcronizedToLock s = new SynchcronizedToLock();
        s.method1();
        s.method2();
    }
}

上面method1和metho2等价
一文解决synchronized_第18张图片

九、深入JVM看字节码monitor指令

(synchronized_demo->SynchronizedDecompilation)

package synchronized_demo;

/**
 * Created by ouyangxizhu on 2019/5/15.
 * 反编译字节码
 */
public class SynchronizedDecompilation {
    private Object object = new Object();
    public void insert(Thread thread){
        synchronized (object){

        }
    }
}

我用的是windows系统,不同系统的指令不一样

  1. 打开cmd
  2. 输入d:
    在这里插入图片描述
  3. 进入类所在路径 cd D:\Chengxu\Myeclipsechengxu\concurrency_demo\src\synchronized_demo
    在这里插入图片描述
  4. 编译 javac SynchronizedDecompilation.java
    在这里插入图片描述
  5. 反编译javap -verbose SynchronizedDecompilation.class
    在这里插入图片描述
  6. 之后可以看到下面的内容
    一文解决synchronized_第19张图片
    monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor(存在对象头中)与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

当一个线程想获得锁时(monitorenter指令)有三种情况

1).monitor计数器为0,该线程会获得锁,之后计数器加一,之后的线程就没办法获得锁了。
2) 如果已经获得锁了,再一次进入同步代码块(可重入性质),计数器会递增。
3)monitor被其他线程获得,本线程阻塞,直到别的线程释放锁。

当一个线程执行monitorexit指令

该指令会使计数器减一。计数为零时锁被释放。

十、可见性

一文解决synchronized_第20张图片
每一个线程会复制主内存的副本(其实是个抽象的概念,真实是放在缓冲区当中的),当线程之间通讯时,是通过先写入主内存,再从主内存当中读出来实现的。
当执行synchronized的同步方法时,在执行之前会从主内存读,执行完毕之前(释放锁之前)会强制写入主内存。

十一、缺陷

1. 效率低

1)锁的释放情况少
方法执行完毕后或者发生异常。(当长时间I/O等情况时,别的线程只能等待)
2)试图获得锁不能设定超时、不能中断一个试图获得锁的线程(Lock类可以)。

2. 不够灵活(读写锁更灵活)

加锁和释放的时机单一,每个锁仅有单一的条件,可能是不够的,对比读写锁(其实读的时候不用加锁)

3. 无法知道是否成功获得锁

(synchronized_demo->LockExample)

package synchronized_demo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Created by ouyangxizhu on 2019/5/15.
 */
public class LockExample {
    public static void main(String[] args){
        ReentrantLock lock = new ReentrantLock();
        lock.lock();//锁住
        lock.unlock();//释放
        boolean b1 = lock.tryLock();//可以知道是否可以获得锁
        boolean b2 = lock.tryLock(3000, TimeUnit.SECONDS);//可以设置超时时间
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();//读写锁
        reentrantReadWriteLock.readLock();//读锁
        reentrantReadWriteLock.writeLock();//写锁
    }
}

十二、synchronized使用注意事项

1. 锁对象不能为空

monitor存储在对象头当中,没有对象当然就没有monitor。

2. 作用域不宜过大

将大部分代码都放到synchronized中会使大部分代码串行工作,影响速度。

3. 避免死锁

避免以下类型代码

synchronized (instance1) {
	synchronized (instance2){
	...
	}            
}
synchronized (instance2) {
	synchronized (instance1){
	...
	}            
}

十三、如何选择Lock和synchronized

1. 尽量都不使用,尽量使用JUC包下的类(使用CAS实现)

2. 尽量使用synchronized,因为可以少写代码。

3. 使用Lock的特性时才用Lock

以上都是为了尽量少出错。使用更简单的,防止复杂。

十四、思考题

1. 多个线程等待同一个synchronized锁的时候,JVM如何选择下一个获取锁的线程。(什么算法?一直等待的?还是刚刚来的线程)

主要看算法,不同版本的JVM不一样(比如等待时间最长的?刚进来的?随机的?)(公平锁 非公平锁)

2. synchronized使得同时只有一个线程可以执行,性能较差,有什么办法可以提升性能?

这个需要看业务
1)synchronized尽量少的包裹代码块
2)可以使用读写锁(防止读的时候加锁)

3. 我想更加灵活的控制获取和释放(现在释放锁的时机已经被规定死了)

可以自己实现接口

4. 什么是锁的升级和降级,什么是JVM的偏向锁,轻量级锁,重量级锁

1)锁只能升级不能降级。
2)偏向锁,轻量级锁,重量级锁是为了优化synchronized关键字实现的,与对象头的标记有关。这里简单介绍一下:”
偏向锁主要解决不需要加锁的情况,即只有一个线程执行或者不竞争
轻量级锁解决少量竞争的情况(才用自旋锁)

十五、总结

一句话概括synchronized

JVM会自动通过使用monitor来加锁和解锁,保证了同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入和不可中断的性质。

你可能感兴趣的:(java,并发,synchronized,并发)