单例模式
单例模式是代码里只有一个实例,因此需要考虑如何防止别人再new一个实例。
思路一(饿汉式)
很简单,把构造函数写成private就可以,然后在类里直接构造一个private实例,只public一个get接口(getInstance)这样就只能获得实例,不能改写,也不能new一个新实例。
另外需要注意的是,创建实例和获得实例的函数都是static类型的,也就是只属于类本身。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | package singleton;
 public class Mgr01 {
 private static final Mgr01 INSTANCE = new Mgr01();
 private Mgr01() {};
 
 public static Mgr01 getInstance() {return INSTANCE;}
 
 public static void main(String[] args){
 Mgr01 m1 = Mgr01.getInstance();
 Mgr01 m2 = Mgr01.getInstance();
 System.out.println(m1 == m2);
 }
 }
 
 | 
另外这是线程安全的,因为jvm保证每一个class只漏到内存一次,因此只初始化一次(多线程访问也没关系)——推荐使用!
思路二
和饿汉式一样,只不过把构造函数改成静态语句块,本质上是一样的。
| 12
 3
 4
 
 | private static final Mgr02 INSTANCE;static {
 INSTANCE = new Merge02{};
 }
 
 | 
思路三(懒汉式)
和饿汉式的区别是什么时候用什么时候初始化。这种方法是调用getInstance的时候才进行初始化(需要加个判断操作),而饿汉式是只要类加载那么就自动初始化。但是也带来了线程不安全的问题,因为有可能多个线程同时调用getInstance函数,在进行if判断的时候为null,然后建立了多个实例。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | package singleton;
 public class Mgr03 {
 private static Mgr03 INSTANCE;
 private Mgr03() {};
 
 public static Mgr03 getInstance() {
 if(INSTANCE == null){
 INSTANCE = new Mgr03();
 }
 return INSTANCE;
 }
 
 public static void main(String[] args){
 Mgr03 m1 = Mgr03.getInstance();
 Mgr03 m2 = Mgr03.getInstance();
 System.out.println(m1 == m2);
 }
 }
 
 | 
思路四
对于懒汉式如何解决线程安全问题,那么就加个锁,但是相应的也会导致性能的下降。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | package singleton;
 public class Mgr04 {
 private static Mgr04 INSTANCE;
 private Mgr04() {};
 
 public static synchronized Mgr04 getInstance() {
 if(INSTANCE == null){
 try{
 Thread.sleep(1);
 }catch (InterruptedException e){
 e.printStackTrace();
 }
 INSTANCE = new Mgr04();
 }
 return INSTANCE;
 }
 
 public static void main(String[] args){
 for(int i=0;i<100;i++){
 new Thread(()->{
 System.out.println(Mgr04.getInstance().hashCode());
 }).start();
 }
 }
 }
 
 | 
思路五:静态内部类方式
jvm保证单例,因此class只会加载一次,MgrHolder也只加载一次,因此保证了线程安全。
加载外部类的时候不会加载静态内部类,保证了懒加载。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | package singleton;
 public class Mrg05 {
 private Mrg05() {
 }
 
 private static class Mrg05Holder {
 private final static Mrg05 Instance = new Mrg05();
 }
 
 public static Mrg05 getInstance() {
 return Mrg05Holder.Instance;
 }
 
 public static void main(String[] args) {
 for(int i=0;i<100;i++){
 new Thread(()->{
 System.out.println(Mgr04.getInstance().hashCode());
 }).start();
 }
 }
 }
 
 | 
思路六
枚举单例,非常鬼才的一种方式,不仅可以解决线程同步,同时可以防止反序列化,因为枚举类没有构造方法。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | package singleton;
 public class Mrg06 {
 INSTANCE;
 
 public static void main(String[] args) {
 for(int i=0;i<100;i++){
 new Thread(()->{
 System.out.println(Mgr06.INSTANCE.hashCode());
 }).start();
 }
 }
 }
 
 | 
除了以上六种,还有在第四种的基础上扩展的两种方法,为了增加效率,减少同步代码块的大小,但是需要额外判断两次instance == null,代码略。这里有一个重点就是需要加上volatile,如果不加volatile可能因为指令重排导致一些问题。
总结:比较常用且完美的方法就是饿汉式(常用简洁)、懒汉式两次判断+静态内部类。枚举单例很完美但是不常用。