Java并发编程 —— synchronized关键字

一、是什么?(作用)

synchronized关键字解决了多个线程之间访问资源的同步性问题,保证了被其修饰的方法或是代码块在任意时刻只能有一个线程执行。

而在早期的Java版本中,synchronized属于重量级锁,效率低下。为什么呢?

因为监视器锁(monitor)是依赖底层的操作系统Mutex Lock来实现的,Java的线程是映射到操作系统的原生线程之上的。如果要挂起或唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。
庆幸的是,在Java 6之后Java官方从JVM层面对synchronized做了较大的优化,所以现在的synchronized锁效率也优化的很不错了。JDK 1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗话、偏向锁、轻量级锁等技术来减少操作的开销。
故而,目前不论是各种开源框架还是JDk源码都大量使用了synchronized关键字。


二、怎么用?(实践)

1、主要有三种使用方式:

  • 修饰实例方法:给当前对象实例加锁。进入同步代码前要获得 当前对象实例的锁
synchronized void method(){
  //业务代码
}
  • 修饰静态方法:给当前类加锁。会作用于当前类的所有对象实例,进入同步代码前要获得当前类的锁。因为静态成员不属于任何一个实例对象,是类成员(static表明这是该类的一个静态资源,无论new了多少对象,只有一份)。所以,如果一个线程A调用一个实例对象的非静态synchronized方法,而线程B需要调用这个实例对象所属类的静态synchronized方法,是允许的,不会发生互斥现象的,因为访问静态synchronized方法占用的锁是当前类的锁,而访问非静态synchronized方法占用的锁是当前实例对象的锁,故而不矛盾。
synchronized static void method(){
  //业务代码
}
  • 修饰代码块:给指定对象加锁。synchronized(thisobject)表示进入同步代码块前要获得thisobject对象的锁synchronized(类.class)表示进入同步代码前要获得指定类的class锁
synchronized(this){
  //业务代码
}

2、小结

  • synchronized关键字加在static静态方法和synchronized(class)代码块上都是给加锁。
  • synchronized关键字加在实例方法上是给对象实例加锁。
  • 尽量不要使用synchronized(String str),因为在JVM中,字符串常量池具有缓存功能!

三、为什么?(底层原理)

synchronized关键字底层原理属于JVM层面。可看 深入理解synchronized底层原理,一篇文章就够了! 这篇文章。


四、面试问题

1、使用双重校验锁实现对象单例(线程安全)

public class Singleton {
  private volatile static Singleton uniqueInstance;

  private Singleton() {}

  public static Singleton getUniqueInstance(){
    //先判断对象是否已实例化过,若没有方可进入加锁代码块
    if(uniqueInstance == null){
      //类对象加锁
      synchronized(Singleton.class){
        if(uniqueInstance == null){
          uniqueInstance = new Singleton();
        }
      }
    }
    return uniqueInstance;
  }
}

需要注意的是uniqueInstance采用volatile关键字修饰是很必要的。为什么呢?uniqueInstance = new Singleton();这段代码其实是分三步执行的:

  • 1、为uniqueInstance分配内存空间
  • 2、初始化uniqueInstance
  • 3、将uniqueInstance指向分配的内存地址

但由于JVM具有指令重排序的特性,执行顺序有可能变成 1 -> 3 -> 2。指令重排序在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程T1执行了1和3,此时T2调用getUniqueInstance()后发现uniqueInstance不为空,因此返回uniqueInstance,但此时uniqueInstance还未被初始化。
此处就不做细分析,简而言之,使用volatile可以禁止JVM的指令重排,保证上述代码在多线程环境下也能正常运行。

2、构造方法能否使用synchronized关键字修饰?

回答:不可以,构造方法本身就是线程安全的,不存在同步的构造方法一说。

posted @ 2021-06-29 13:41  _天青色烟雨  阅读(77)  评论(0编辑  收藏  举报