单例模式
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:私有化构造方法
以下是单例模式7种设计方式以及特点说明:
1,饿汉模式
package com.zl.Singleton; //类加载到内存后就实例化一个对象,JVM保证线程安全
//经济实惠,推荐使用
//唯有一点小不足:不管用到与否,类装载时就完成实例化
public class Singleton01 { private static final Singleton01 singleton = new Singleton01(); private Singleton01(){ } public static Singleton01 getInstance(){ return singleton; } }
2,懒汉式
package com.zl.Singleton; import java.util.concurrent.TimeUnit; //达到了按需初始化的目的,但是带来了更大的问题:线程不安全 public class Singleton02 { private static Singleton02 singleton; private Singleton02(){ } public static Singleton02 getInstance(){ if(singleton == null){ //模拟业务场景,费点时间 try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } singleton = new Singleton02(); } return singleton; } //测试多线程下,单例模式不成立(多线程非安全) public static void main(String[] args) { for (int i = 0; i < 100; i++) { //jdk1.8之后的lambda表达式 new Thread(()-> System.out.println(Singleton02.getInstance().hashCode()) ).start(); } } }
3,懒汉式+ synchronized
package com.zl.Singleton; import java.util.concurrent.TimeUnit; //达到了想要的目的,但效率底下 public class Singleton03 { private static Singleton03 singleton; private Singleton03(){ } public static synchronized Singleton03 getInstance(){ if(singleton == null){ //模拟业务场景,费点时间 try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } singleton = new Singleton03(); } return singleton; } //测试多线程下,单例模式成立(多线程安全) public static void main(String[] args) { for (int i = 0; i < 100; i++) { //jdk1.8之后的lambda表达式 new Thread(()-> System.out.println(Singleton03.getInstance().hashCode()) ).start(); } } }
4,懒汉式+ synchronized+妄想通过减少同步代码块提高效率(实际不可行)
package com.zl.Singleton; import java.util.concurrent.TimeUnit; //达到了想要的目的,但效率底下 public class Singleton04 { private static Singleton04 singleton; private Singleton04(){ } public static Singleton04 getInstance(){ if(singleton == null){ synchronized(Singleton04.class){ //模拟业务场景,费点时间 try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } singleton = new Singleton04(); } } return singleton; } //测试多线程下,单例模式不成立(多线程不安全) public static void main(String[] args) { for (int i = 0; i < 100; i++) { //jdk1.8之后的lambda表达式 new Thread(()-> System.out.println(Singleton04.getInstance().hashCode()) ).start(); } } }
不可行分析:当A线程来调用 getInstance() 方法,判断 singleton 不为空,在A线程还没有把 singleton new出来,B线程也来调用 getInstance() 方法,判断 singleton 不为空,又new了一次
5,看似完美的“双重校验锁(double check lock)”
package com.zl.Singleton; import java.util.concurrent.TimeUnit; //达到了想要的目的,看似完美,en... 确实也算不错 public class Singleton05 { private volatile static Singleton05 singleton;//思考为什么加volatile? private Singleton05() { } public static Singleton05 getInstance() { if (singleton == null) { synchronized (Singleton05.class) { if (singleton == null) { //模拟业务场景,费点时间 try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } singleton = new Singleton05(); } } } return singleton; } //测试多线程下,单例模式成立(多线程安全) public static void main(String[] args) { for (int i = 0; i < 100; i++) { //jdk1.8之后的lambda表达式 new Thread(() -> System.out.println(Singleton05.getInstance().hashCode()) ).start(); } } }
思考实例变量为什么加volatile?
Java JVM内部有一个优化,会对汇编指令进行重排序,会在对象半初始化状态返回对象,造成数据丢失。
我们new一个对象时,JVM内部不是一步到位的,如 Object object = new Object();
它先new了一个对象,然后初始化,指向分配内存地址,return
加volatile是为了禁止指令重排序
扩展:一线大厂面试题。
面试官:知道单例模式吗?了解过double check lock的单例模式吗?为什么实例变量要加volatile?
6,完美的静态内部类实现
package com.zl.Singleton; //完美的方法 //JVM保证单例,类只加载一次 //加载外部类时不会加载内部类 public class Singleton06 { private Singleton06() { } private static class Singleton06Holder{ private final static Singleton06 singleton = new Singleton06(); } public static Singleton06 getInstance() { return Singleton06Holder.singleton; } //测试多线程下,单例模式成立(多线程安全) public static void main(String[] args) { for (int i = 0; i < 100; i++) { //jdk1.8之后的lambda表达式 new Thread(() -> System.out.println(Singleton06.getInstance().hashCode()) ).start(); } } }
7,更加完美的方法?有,枚举类(无构造方法)
Java创始人之一Joshua 在Effective Java 一书提到这种方式
package com.zl.Singleton; //不仅解决线程同步,而且可以防止方序列化 public enum Singleton07 { singleton; public static void main(String[] args) { for (int i = 0; i < 100; i++) { //jdk1.8之后的lambda表达式 new Thread(() -> System.out.println(singleton.hashCode()) ).start(); } } }