单例模式
单例模式
某个类只能存在一个对象实例,且该类只提供一个取得其对象实例的方法(静态方法)
使用场景:需要频繁创建和销毁对象或创建对象消耗过多又经常用到比如数据源,session工厂
饿汉式(静态常量)
1.构造器私有化(防止外部new)
2.类的内部创建对象(private,final,static)
3.向外暴露一个静态的公共方法,getInstance
class Singleton{
//构造器私有化,外部不能new
private Singleton(){
}
private final static Singleton instance=new Singleton();
public static Singleton getInstance(){
return instance;
}
}
优点:类装载时就完成了实例化,避免了线程同步问题
缺点:类装载时就完成了实例化,可能从未用过这个实例从而造成内存的浪费
饿汉式(静态代码块)
class Singleton{
//构造器私有化,外部不能new
private Singleton(){
}
private static Singleton instance;
static{
instance=new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}
这种方式实际和上面的类似,只不过把创建对象放在了静态代码块中
线程不安全懒汉式
class Singleton{
private static Singleton instance;
private Singleton(){}
//当使用时再创建instance
pubulic static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
优点:能起到懒加载的效果但只能在单线程下使用
缺点:在多线程下,如果一个线程进入到if(instance==null)但还没往下执行,另一个线程也通过了这个判断语句,此时就会创建多个实例
线程安全懒汉式
class Singleton{
private static Singleton instance;
private Singleton(){}
//加入同步代码块
pubulic static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
效率太低了,每次在执行getInstance( )方法都要进行同步,但实际上我们只需要在第一次创建instance时同步即可
同步代码块懒汉式
class Singleton{
private static Singleton instance;
private Singleton(){}
//加入同步代码块
pubulic static synchronized Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
instance=new Singleton();
}
}
return instance
}
}
实质上并没有实现线程安全
双重检查(推荐使用)
class Singleton{
//使用volatile利用可见性通知其他线程instance的实时情况
private static volatile Singleton instance;
private Singleton(){}
//当使用时再创建instance
pubulic static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
//这里因为可见性的作用,一旦有线程创建instance,这个if不会执行
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
为什么要双重检查?
双重校验锁第一次进行判空原因:当程序顺序执行的时候,如果不进行判空,每一个线程都会先去获得当前类的类锁,而其他线程都进入阻塞状态。单例模式中初始化单例的程序只会执行一次,大部分情况下会直接到return语句返回,如果都阻塞在获取锁的位置,会大大降低程序的运行速度双重校验锁
第二次进行判空原因:假设有两个线程A和B,都进行完第一次判空了,A和B都阻塞在如图所示位置
这个时候A线程获取了类锁,然后B线程被阻塞,A线程新建了一个实例后释放了锁,B线程获取锁,又新建了一个实例,这破坏了单例设计模式的初衷。
为什么使用volatile关键字?
instance = new Singleton(); 这段代码其实是分为三步执行:
- 为 instance 分配内存空间
- 初始化 instance
- 将 instance指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance () 后发现 instance 不为空,因此返回instance ,但此时 instance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运⾏。
静态内部类
class Singleton{
//构造器私有化,外部不能new
private Singleton(){
}
//写一个静态内部类,该类有一个静态属性
private static class SingletonInstance{
private static final Singleton INSTANCE=new Singleton;
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
静态内部类在类装载时并不会立刻被实例化,而是在调用getInstance()时才实例化,同时利用了JVM保证了线程的安全性
枚举方式
enum Singleton{
INSTANCE;
}
单例模式的应用
java.lang.Runtime → 饿汉式