synchronized关键字

synchronized的作用

  • 官网解释翻译

    同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。

  • 通俗解释

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

synchronized的地位

  • synchronized的java的关键字,被java语言原生支持
  • 是最基本的互斥同步手段
  • 是并发编程种的元老级角色,是并发编程的必学内容

synchronized的两种用法

对象锁

  • 包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)

代码块形式

  • 手动指定锁对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class SynchronizedObjectCodeBlock2 implements Runnable {

    public static void main(String[] args) {

    SynchronizedObjectCodeBlock2 s = new SynchronizedObjectCodeBlock2();
    Thread t1 = new Thread(s);
    Thread t2 = new Thread(s);
    t1.start();
    t2.start();
    while (t1.isAlive() || t2.isAlive()) {
    }
    System.out.println("-----");

    }


    @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() + "运行结束");
    }

    }
    }

方法锁形式

  • synchronized修饰普通方法,锁对象默认为this
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class SynchronizedObjectCodeBlock2 implements Runnable {

    public static void main(String[] args) {

    SynchronizedObjectCodeBlock2 s = new SynchronizedObjectCodeBlock2();
    Thread t1 = new Thread(s);
    Thread t2 = new Thread(s);
    t1.start();
    t2.start();
    while (t1.isAlive() || t2.isAlive()) {
    }
    System.out.println("-----");
    }

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

类锁

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

概念

  • Java类可能有很多实例对象,但只有1个class对象
  • 所谓的类锁,不过是class对象的锁而已
  • 类锁只能在同一时间被一个对象拥有
  • synchronized加在static方法上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class SynchronizedObjectCodeBlock2 implements Runnable {

    public static void main(String[] args) {

    SynchronizedObjectCodeBlock2 s1 = new SynchronizedObjectCodeBlock2();
    SynchronizedObjectCodeBlock2 s2 = new SynchronizedObjectCodeBlock2();
    Thread t1 = new Thread(s1);
    Thread t2 = new Thread(s2);
    t1.start();
    t2.start();
    while (t1.isAlive() || t2.isAlive()) {
    }
    System.out.println("-----");
    }

    @Override
    public void run() {
    test();
    }
    private static synchronized void test() {
    System.out.println(Thread.currentThread().getName());
    try {
    Thread.sleep(3000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    }
  • synchronized(*.class)代码块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class SynchronizedObjectCodeBlock2 implements Runnable {
    public static void main(String[] args) {
    SynchronizedObjectCodeBlock2 s1 = new SynchronizedObjectCodeBlock2();
    SynchronizedObjectCodeBlock2 s2 = new SynchronizedObjectCodeBlock2();
    Thread t1 = new Thread(s1);
    Thread t2 = new Thread(s2);
    t1.start();
    t2.start();
    while (t1.isAlive() || t2.isAlive()) {
    }
    System.out.println("-----");

    }
    @Override
    public void run() {
    synchronized (SynchronizedObjectCodeBlock2.class) {

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

多线程访问同步方法的7种情况

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

  • 会串行执行
  • 同步方法默认锁的是this,也就是当前对象,每次只能有一个线程获取到锁

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

  • 会并行执行
  • 默认锁的是this,也就是当前对象,由于是来个对象,也就是不同的锁,所以两个线程会同时执行

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

  • 会串行执行
  • 静态方法锁的是class对象,class对象只有一个,所以两个线程是同一把锁

两个线程同时访问同步方法与非同步方法

  • 会并行执行
  • 同步方法会受到保护,非同步方法不会上锁,所以会并行执行

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

  • 会串行执行
  • 同步方法锁的是this当前对象,即便是不同的方法,锁的也是同一个对象,所以会串行执行

同时访问静态synchronized和非静态synchronized方法

  • 并行执行
  • 静态方法锁的class类,非静态方锁的是当前对象,两个方法并不是同一个锁,所以会并行执行

方法抛异常后,会释放锁

  • 异常之后会释放锁,不会影响后续的执行
  • 先抛出异常,后面才能进入同步方法

性质

可重入

  • 什么是可重入:指的是同一个线程的外层函数获得锁之后,内层函数可以直接再次获取该锁
  • 好处:避免死锁,提升封装性

    比如方法1被synchronized修饰,在方法里面递归调用该方法,也是可以重入的
    方法1和方法2都被synchronized修饰,那么在方法1中也是可以调用方法2的

不可中断

  • 一旦这个锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,直到别人的线程释放这个锁,如果别人永远不释放锁,那么我只能永远地等待下去。

原理

加锁和释放锁的原理

  • 获取呵呵释放锁的时机:进入和退出同步代码块(运行完毕或者抛出异常)
  • 等价代码(使用lock锁必须要显示的上锁解锁,并且需要考虑异常情况,synchronized则不需要考虑这些)
  • 看字节码:monitor相关指令
    使用javac xxx.java将java文件编译成class文件
    再使用java -v xxx.class反编译class文件
    具体代码如下:
    1
    2
    3
    4
    5
    6
    7
    public class Decompilation {
    private Object object = new Object();
    public void insert(Thread thread){
    synchronized (object){
    }
    }
    }
    反编译后,insert方法如下:
    Decompilation反编译内容
    从图中可以看出,第6行monitorenter指令,上锁
    第8行monitorexit指令,释放锁
    第14行monitorexit指令,则是在抛出异常的情况下释放锁

可重入原理:加锁次数计数器

  • JVN会记录被加锁大的次数
  • 第一次加锁时,次数从0变为1,之后如果再次加锁,就从1变成2,以次类推
  • 退出一层同步代码块时,计数减一,当计数为0的时候表示锁释放

可见性

  • 一个线程执行的结果,另一个线程不一定可见
  • 线程1操作x=5,之后线程2可能读取到之前的数据x=3
  • synchronized可以保证可见性

缺陷

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

常见面试题

  • 使用注意点:锁的范围不宜过大、避免锁的嵌套
  • 如何选择Look和synchronized关键字?

    如果synchronized能够满足需求的话,优先使用synchronized,无法满足再使用Lock锁

  • 多线程访问同步方法的各种具体情况

    上面所述的七种情况