synchronized关键字

说一说对synchronized关键字的了解

synchronized关键字解决的是多线程之间访问资源的同步性。synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外在java早期版本中,synchronized属于重量级锁,效率低下。
因为监视器锁是依赖于底层的操作系统的Mutex Lock来实现的,java的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换需要从用户态转换到内核态,这个状态之间的转换都需要相对比较长的时间,时间成本相对较高。
庆幸的是在java6之后java官方对jvm层面对synchronized较大优化,所以现在synchronized锁效率优化的也不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁,适应性自旋锁,锁消除,锁粗化,偏向锁,轻量级锁等技术来减少锁操作的开销。
所以你会发现目前的话,不论是各种开原框架还是JDK源码都大量使用了synchronized关键字。

说说是怎么使用synchronized关键字的

synchronized关键字最主要的三种使用方式:

  1. 修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
  2. 修饰静态方法:也就是给当前类加锁,会作用于类的所有对象实例,进入同步代码前要获得当前class的锁。因为静态成员不属于任何一个实例对象,是类成员(static修饰的是静态资源,不管new了多少个对象,只有一份)。所以,如果一个线程A调用一个实例对象的非静态synchronized方法,而线程B需要调用这个实例对象所属的类的静态synchronized方法,是允许的,不会发生互斥现象。因为访问静态synchronized方法占用的锁是类的锁,而访问非静态synchronized方法占用的锁是当前实例对象的锁。
  3. 修饰代码块:指定加锁对象,对给定对象/类加锁。synchronized(this|object)表示进入同步代码块前要获得给定对象的锁。synchronized(类.class)表示进入同步代码块前要获得当前class的锁。

总结:

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

下面说一个常见的 双重检测锁机制实现对象单例(线程安全)


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关键字修饰。
这里需要的不是volatile的可见性,而是防止指令重排。
本质上new XXX()是分为三步的

  1. 为实例对象分配内存空间
  2. 初始化实例对象
  3. 将对象指向分配的内存地址

但是由于jvm可能指令重排,也就是1,2,3可能实际上是1,3,2执行的。指令重排单线程下不会出问题,但是多线程的话可能在1,3执行完有线程调用这个对象,也就是实际上虽然对象还没初始化,但是因为不为空但是被调用了。就会出问题。

构造方法可以使用synchronized关键字修饰么?

构造方法不能使用synchronized关键字修饰。
因为构造方法本身就属于线程安全的,不存在同步的构造方法一说。

讲一下synchronized关键字的底层原理

synchronized关键字底层原理属于JVM层面。
synchronized修饰同步语法块的实现主要是实用monitorenter和monitorexit指令。其中monitorenter指令指向同步代码块开始的位置,monitorexit指令则指明同步代码块结束的位置。
当执行monitorenter指令时,线程试图获取锁也就是获取对象监视器monitor的持有权。

在java虚拟机(HotSpot)中,monitor是基于C++实现的,由 ObjectMonitor实现,每个对象中都内置了一个ObjectMonitor对象。
另外,wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才可以调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为0则表示锁可以被获取,获取后锁的计数器设为1.也就是加1。