单例模式详解
单例模式(创建型模式)
基本介绍
单例模式指在整个应用中,某个类只存在一个实例对象,且该类只提供一个取得其对象实例的方法。
应用
JDK的Runtime中使用到了饿汉式单例模式
单例模式的八种方式
(1)饿汉式(静态常量)
(2)饿汉式(静态代码块)
(3)懒汉式(线程不安全)
(4)懒汉式(线程安全,同步方法)
(5)懒汉式(线程安全,同步代码块)
(6)双重检查
(7)静态内部类
(8)枚举
饿汉式(静态常量)(可以使用)
代码实现
//饿汉式(静态常量)
public class Hunger {
private Hunger(){
}
private static final Hunger instance=new Hunger();
public static Hunger getInstance(){
return instance;
}
}
优缺点
-
优点:写法简单,类加载时完成了实例化,避免了线程同步问题。
-
缺点:类加载时完成了实例化,没达到lazy loading(惰性加载)的效果,若从不会用到这个实例会造成内存浪费。
原因:不一定是通过调用getInstance()方法导致类加载,有可能是其他的方式导致类加载
-
总结:这种单例模式可用,可能造成内存浪费。
饿汉式(静态代码块)(可以使用)
代码实现
//饿汉式(静态代码块)
public class Hunger {
private Hunger(){
}
private static Hunger instance;
static {
instance=new Hunger();
}
public static Hunger getInstance(){
return instance;
}
}
优缺点
同上述静态属性的优缺点
懒汉式(线程不安全)
代码实现
public class Lazy {
private Lazy(){
}
private static Lazy instance;
public static Lazy getInstance() {
if(instance==null){
instance=new Lazy();
}
return instance;
}
}
优缺点
- 优点:起到了lazy loading的效果,但只能在单线程下使用,
- 缺点:在多线程下可能产生多个实例,多线程不安全
- 结论:实际开发中不建议使用。
懒汉式(线程安全,同步方法)
代码实现
//懒汉式(线程安全)
public class Lazy {
private Lazy(){
}
private static Lazy instance;
//加入同步处理代码,解决线程不安全问题
public static synchronized Lazy getInstance() {
if(instance==null){
instance=new Lazy();
}
return instance;
}
}
优缺点
- 优点:解决了线程不安全问题
- 缺点:效率低,每个线程想要获得类的实例时,执行getInstance()方法都要进行同步,
- 实际开发时不推荐使用
懒汉式(线程安全,同步代码块)
代码实现
//懒汉式(线程安全)
public class Lazy {
private Lazy(){
}
private static Lazy instance;
public static Lazy getInstance() {
if(instance==null){
//此处加入了同步代码块,当两线程都进入了判断语句,还是有可能造成两个实例的产生。
synchronized (Lazy.class){
instance=new Lazy();
}
}
return instance;
}
}
优缺点
- 这种方法本意是对懒汉式同步方法的改进,因为懒汉式同步方法的效率太低。将其更改为同步产生实例的代码块
- 但这种同步方法并不能起到线程同步的作用,只要两个线程都进入了判断语句,还是有可能产生多个实例。
- 实际开发中不使用。
双重检查(推荐使用)
代码实现
//双重检测(线程安全)
public class Lazy {
private Lazy(){
}
//volatile:保证了一致性,不保证原子性,禁止指令重拍
private volatile static Lazy instance;
public static Lazy getInstance() {
if(instance==null){
//加入同步代码块
synchronized (Lazy.class){
//进行第二次检测
if(instance==null){
instance=new Lazy();
}
}
}
return instance;
}
}
优缺点
- Double-Check概念是多线程开发中经常使用的,进行了两次if(instance==null)检查,保证了线程安全。
- 这样的设计使得实例化代码只执行一次:后面的·1访问都是直接return实例化对象,避免反复进行方法同步。
- 优点:线程安全,延迟加载,效率较高,
- 结论:实际开发中建议使用。
静态内部类(推荐使用)
代码实现
//静态内部类(线程安全)
public class Lazy {
private Lazy(){
}
public static Lazy getInstance(){
return inner.INSTANCE;
}
static class inner{
private static final Lazy INSTANCE=new Lazy();
}
}
优缺点
- 采用了类装载的机制保证了初始化实例时,只有一个线程。
- 静态内部类方法在此类被加载时不会立即实例化,只有在调用getInstance()方法时才会加载其内部类并完成此类的实例化。
- 类的静态属性只会在第一次加载类时初始化,此处,JVM帮助我们保证了线程的安全性。类初始化时,别的线程无法进入。
- 优点:避免了线程不安全,使用静态内部类的特点实现了延迟加载,效率高。
- 结论:推荐使用。
枚举(推荐使用)
代码实现
public enum Singleton {
INSTANCE;//属性
public void sayOk(){
System.out.println("ok~~");
}
}
优缺点
- 优点:借用枚举来实现单例模式,不仅能避免多线程同步问题,而且可以防止反序列化重新创建新的对象。
- 结论:推荐使用
注意事项
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
- 要实例化一个单例类时,使用相应获取对象的方法而不是new();
- 单例模式使用场景:需要频繁进行创建或销毁的对象;创建对象时耗时过多或耗费资源过多(重量级对象)且经常使用的对象,工具类对象,频繁访问数据库或文件的对象(如:数据源,session工厂等 )