1.单例模式简介
单例模式,就是采取一定的方法保证在整个的软件系统中,对某一个类只存在一个对象实例,并且该类只提供一个取得其对象的方法。
2.单例模式的实现方法
单例模式的实现方法分为8种,主要分为:
1.饿汉式(静态常量)(√)
2.饿汉式(静态代码块)(√)
3.懒汉式(线程不安全)
4.懒汉式(线程安全,同步方法)
5.懒汉式(线程安全,同步代码块)
6.双重检查(√)
7.静态内部类(√)
8.枚举类(√)
2.1: 饿汉式(静态常量)
核心代码: private static SingletonByStatic instance = new SingletonByStatic();
优点:简单,在类装载的时候已经完成了实例化,避免了线程同步
缺点:在类装载的时候就完成实例化,没有达到 lazyloading(懒加载),如果从始至终没有使用过这个实例,则会造成内存的浪费 。导致类装载的原因可能有很多种,这时候万一初始化了instance,未使用。造成了内存的浪费
完整代码:
package com.liz.GOF23.singleton.hungry_man;
//饿汉式(静态变量)
//优点: 简单,在类装载的时候已经完成了实例化,避免了线程同步
//缺点: 在类装载的时候就完成实例化,没有达到 lazyloading(懒加载),如果从始至终没有使用过这个实例,则会造成内存的浪费
//导致类装载的原因可能有很多种,这时候万一初始化了instance,未使用。造成了内存的浪费
//!!!可以使用,可能造成内存浪费
public class SingletonByStatic {
//1.构造器私有化,外部不能new
private SingletonByStatic(){
}
//2.本类内部创建对象实例(在类加载的时候进行定义 由于后期的构造器私有化 所以不能修改)
private static SingletonByStatic instance = new SingletonByStatic();
//3.对外提供返回 返回实例对象
public static SingletonByStatic getInstance(){
return instance;
}
public static void main(String[] args) {
//测试
SingletonByStatic instance = SingletonByStatic.getInstance();
SingletonByStatic instance1 = SingletonByStatic.getInstance();
System.out.println(instance == instance1); //true
System.out.println(instance.hashCode()); //完全一致
System.out.println(instance1.hashCode());//完全一致
}
}
2.2: 饿汉式 (静态代码块)
核心代码:
static{
instance=new SingletonByStaticBlock();
}
优点:类加载的时候完成初始化,以静态代码块的形式完成了类的实例化,在类装载的时候,就执行静态代码块中的代码,初始化类的实例。
缺点:可能造成内存浪费
完整代码:
package com.liz.GOF23.singleton.hungry_man;
/**
* 饿汉式(静态代码块)
* 类加载的时候完成初始化
*
* 这种方法和上面的方法类似,只不过将类实例化的过程放在了静态代码块中。也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上述一致
* 这种单例模式可用,但是可能造成内存浪费
* */
public class SingletonByStaticBlock {
private static SingletonByStaticBlock instance;//静态代码
//在静态代码块中创建单例对象
static{
instance=new SingletonByStaticBlock();
}
private SingletonByStaticBlock(){}//防止对类进行修改
public static SingletonByStaticBlock getInstance() {
return instance;
}
public static void main(String[] args) {
System.out.println(SingletonByStaticBlock.getInstance().hashCode() == SingletonByStaticBlock.getInstance().hashCode());
}
}
2.3 懒汉式(线程不安全)
核心代码:
if(instance == null){
//延迟加载
instance=new SingleBySync();
}
优点:实现了懒加载,提高了效率
缺点:只能在单线程下使用
完整代码:
public class SingleByLazyLoading{
private static SingleByLazyLoading instance;
private SingleByLazyLoading(){}
public static SingleByLazyLoading getInstance(){
//实现懒加载
if(instance == null){
//延迟加载
instance=new SingleByLazyLoading();
}
return instance;
}
public static void main(String[] args) {
System.out.println(SingleByLazyLoading.getInstance() ==(SingleByLazyLoading.getInstance()));
}
}
2.4 懒汉式(同步方法)
核心代码:public static synchronized SingleBySync getInstance(){}
优点:解决了线程不安全的问题
缺点:效率太低。当每个线程想获得类实例的时候,执行getInstance方法都要进行同步,而这个方法同步一次就够了,后面想获得该实例直接返回即可。所以说效率过低(实际开发中不建议使用)
完整代码:
package com.liz.GOF23.singleton.lazy_man;
/**
* 加重量级锁 保证线程安全
* 懒汉式(线程安全,同步方法) 在使用的时候才进行创建
*
* 优点: 解决了线程不安全的问题
* 缺点:效率太低。当每个线程想获得类实例的时候,执行getInstance方法都要进行同步,而这个方法同步一次就够了,后面想获得该实例直接返回即可。所以说效率过低
*
* !!!在实际开发中,不推荐使用(效率过低)
* */
public class SingleBySync {
private static SingleBySync instance;
private SingleBySync(){}
public static synchronized SingleBySync getInstance(){
if(instance == null){
//延迟加载
instance=new SingleBySync();
}
return instance;
}
//提供一个静态的公有方法,当使用到该方法时,才去创建instance 同时加上了synchronized 保证线程同步
public static void main(String[] args) {
System.out.println(SingleBySync.getInstance() == (SingleBySync.getInstance()));
}
}
2.5 懒汉式 (同步代码块)
核心代码:
synchronized(SingletonByNotSafeThread.class){
instance = new SingletonByNotSafeThread();
}
优点: 起到了懒加载的效果,但是只能在单线程下使用
缺点: 多线程下无效(一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时候就会出现了多个实例。所以在多线程下不可取),同时效率还低.
完整代码:
package com.liz.GOF23.singleton.lazy_man;
/**
* 懒汉式(线程不安全)【当使用某个方法时,才去创建instance】
* 在实例化方法处写逻辑
*
* 优缺点声明:
* 优点: 起到了懒加载的效果,但是只能在单线程下使用
* 缺点: 如果在多线程下,一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时候就会出现了多个实例。所以在多线程下不可取
*
* !!! 实际开发中,不可取
* */
public class SingletonByNotSafeThread {
private static SingletonByNotSafeThread instance;
private SingletonByNotSafeThread(){}
//不安全 效率还低(白给自爆性) 不能使用
public static SingletonByNotSafeThread getInstance(){
if(instance == null){
synchronized(SingletonByNotSafeThread.class){
instance = new SingletonByNotSafeThread();
}
}
return instance;// 依旧不能保证线程安全
}
//提供一个静态的公有方法,当使用到该方法时,才去创建instance
public static void main(String[] args) {
System.out.println(SingletonByNotSafeThread.getInstance() == (SingletonByNotSafeThread.getInstance()));
}
}
2.6 双重检查
核心代码:
//volatile: 立即更新到主存,同时保证了指令不重排序
private static volatile SingleByDoubleCheck singleton;
//双重检查代码
if(singleton == null){
synchronized (SingleByDoubleCheck.class){
if(singleton == null){
singleton=new SingleByDoubleCheck();
}
}
}
优点: 线程安全,同时使用了延迟加载,效率高
缺点:代码略微复杂
全部代码:
package com.liz.GOF23.singleton.double_check;
/**
* 双重选择:
* 进行了两次if(singleton == null)的检查,保证了线程安全
* 这样实例化代码只用执行一次,后面再次访问时,直接return即可
*
* 线程安全: 延迟加载,效率较高
* 结论: 在实际开发中,用的较多
* */
public class SingleByDoubleCheck {
//volatile: 立即更新到主存,同时保证了指令不重排序
private static volatile SingleByDoubleCheck singleton;
private SingleByDoubleCheck(){}
//提供一个静态的公有方法,加入双重检查代码。解决线程安全问题和懒加载问题
public static SingleByDoubleCheck getInstance(){
if(singleton == null){
synchronized (SingleByDoubleCheck.class){
if(singleton == null){
singleton=new SingleByDoubleCheck();
}
}
}
return singleton;
}
public static void main(String[] args) {
System.out.println(SingleByDoubleCheck.getInstance() == SingleByDoubleCheck.getInstance());
}
}
2.7 静态内部类
核心代码
//静态内部类
private static class InnerClass {
private static final StaticInnerClassDemo INSTANCE = new StaticInnerClassDemo();
}
优点:
1.避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。
2.达到了懒加载的效果{外部类的加载不会导致内部类的加载}
3.类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性。在类进行初始化时,其他线程无法进入
4.推荐使用
核心代码:
public class StaticInnerClassDemo {
private StaticInnerClassDemo() {
}
private static class InnerClass {
private static final StaticInnerClassDemo INSTANCE = new StaticInnerClassDemo();
}
/**
* 静态内部类
* 1.当主类进行类装载的时候,静态内部类不会进行装载
* 2.当调用方法,用到了静态内部类时,内部类会进行装载
*/
public static StaticInnerClassDemo getInstance(){
return InnerClass.INSTANCE;
}
public static void main(String[] args) {
System.out.println(StaticInnerClassDemo.getInstance() == StaticInnerClassDemo.getInstance());
}
}
2.8: 枚举类
核心代码: 建立一个枚举类型
package com.liz.GOF23.singleton.enum_singleton;
//枚举
public enum Singleton {
INSTANCE;//
private SingletonByEnumDemo demo = null;
private Singleton() {
demo = new SingletonByEnumDemo();
}
public SingletonByEnumDemo getInstance() {
return demo;
}
}
优点:
借助jdk1.5中添加的枚举类实现单例模式,不但可以避免多线程同步问题,而且防止反序列化重新创建新的对象
同时也采用了懒加载的思想
完整代码:
package com.liz.GOF23.singleton.enum_singleton;
//枚举
public enum Singleton {
INSTANCE;//
private SingletonByEnumDemo demo = null;
private Singleton() {
demo = new SingletonByEnumDemo();
}
public SingletonByEnumDemo getInstance() {
return demo;
}
}
package com.liz.GOF23.singleton.enum_singleton;
/**
* 借助jdk1.5中添加的枚举类实现单例模式,不但可以避免多线程同步问题,而且防止反序列化重新创建新的对象
* */
public class SingletonByEnumDemo {
public static void main(String[] args) {
SingletonByEnumDemo instance1 = Singleton.INSTANCE.getInstance();
SingletonByEnumDemo instance2 = Singleton.INSTANCE.getInstance();
System.out.println(instance1 == instance2);
}
}
3.单例模式总结
1.单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2.当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new (getInstance()..)
3.单例模式使用场景:
<1>.需要频繁的进行创建和销毁的对象
<2>.创建对象时耗时过多或者耗费资源过多(即:重量级对象),但是又经常用到的对象
<3>.工具类对象
<4>.频繁访问数据库或者文件的对象(sessionFactory or 数据源)
4.jdk中使用到单例模式的栗子: java.lang.Runtime(使用了上述的2.1 采用了静态常量的饿汉式实现)