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
采用 volatile
关键字修饰也是很有必要的, uniqueInstance = new Singleton();
这
段代码其实是分为三步执行:
- 为
uniqueInstance
分配内存空间 - 初始化
uniqueInstance
- 将
uniqueInstance
指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不
会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执
行了 1 和 3,此时 T2 调用getUniqueInstance ()
后发现uniqueInstance
不为空,因此返回uniqueInstance
,但此时uniqueInstance
还未被初始化。使用volatile
可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
2.说一下对synchronized关键字的理解?
synchronized
关键字解决的是多个线程之间访问资源的同步性, synchronized
关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中, synchronized
属于 重量级锁,效率低下。
为什么呢?
因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映
射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而
操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较
⻓的时间,时间成本相对较高。
庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对 synchronized 较大优化,所以现在的synchronized
锁效率也优化得很不错了。JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适
应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
3.怎么使用synchronized关键字的?
synchronized 关键字最主要的三种使用方式:
1.修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁
synchronized void method() {
//业务代码
}
2.修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当
前 class 的锁。因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个
静态资源,不管 new 了多少个对象,只有一份)。所以,如果一个线程 A 调用一个实例对象的
非静态 synchronized
方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized
方法,
是允许的,不会发生互斥现象,因为访问静态 synchronized
方法占用的锁是当前类的锁,而访
问非静态 synchronized
方法占用的锁是当前实例对象锁。
synchronized void staic method() {
//业务代码
}
3.修饰代码块 :指定加锁对象,对给定对象/类加锁。 synchronized(this|object)
表示进入同步代码
库前要获得给定对象的锁。 synchronized(类.class)
表示进入同步代码前要获得 当前 class 的锁
总结
- synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class
类上锁。 - synchronized 关键字加到实例方法上是给对象实例上锁。
- 尽量不要使用 synchronized(String a) 因为 JVM 中,字符串常量池具有缓存功能!
4.synchronized底层原理?
synchronized
同步语句块的实现使用的是 monitorenter
和 monitorexit
指令,其中monitorenter
指令指向同步代码块的开始位置, monitorexit
指令则指明同步代码块的结束位
置。synchronized
修饰的方法并没有 monitorenter
指令和 monitorexit
指令,取得代之的确实是ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法。
不过两者的本质都是对对象监视器 monitor
的获取。