单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例,为什么做这种设计,有些现实服务中设备只有一个,不可能让一个设备同时去做相同的服务给多人,就需要单例模式进行控制了。
通常来说单例模式分为懒汉式与饿汉式,其中又有许多细节划分,不过我觉得很多东西多余初学者来说没有意义,只是随着时间的增长慢慢理解。
单例模式都是跟工厂模式结合的,通常来说都是工厂方法模式
单利模式创建遵循以下概念:
1,构造方法私有化
2,提供一个公开的方法获取类的实例
懒汉与饿汉的区别:
懒汉在需要调用实例的时候才会第一次创建,饿汉则在类加载时创建自身的实例。
懒汉:
懒汉式-多线程可能会出现创建多个实例
单例类 -
//懒汉 public class Singleton { private static Singleton singleton; public static Singleton getSingleton() { if (singleton == null) { singleton = new Singleton(); } return singleton; } public void print(){ System.out.println(singleton.hashCode()); } }
测试一下 ,通过打印hashcode 可以查看实例在内存中是否为同一个引用
创建一个线程类
public class Thread extends java.lang.Thread { @Override public void run(){ try { Singleton.getSingleton().print(); } catch (InterruptedException e) { e.printStackTrace(); } } }
来测试:
public class MainTest { public static void main(String[] args){ Thread thread;//这是上面的线程类 for (int num = 0; num<=100 ;num++) { thread= new Thread(); thread.start(); } } }
控制台打印:
控制台:
可以看出,单利模式已经成功了,所有线程在内存中都是调用的同一个实例的引用,可能因为cpu速率太快,每个线程都完整的跑完了,可这不是我要的效果。我要测试的是在在多线程情况下,可能创建多个实例的情况;
Thread.sleep(1000L)会让当前线程休眠一秒,并且不释放锁。
修改单例类:
public class Singleton { private static Singleton singleton; public static Singleton getSingleton() throws InterruptedException { if (singleton == null) { Thread.sleep(1000L);//休眠不释放锁,让其他线程进入,因为方法不是同步的,没有阻塞,所以其他方法也可以进入 singleton = new Singleton(); } return singleton; } public void print(){ System.out.println(singleton.hashCode()); } }
测试:
已经不能实现单例的需求
有一个方法就是加同步,来支持多线程操作
//懒汉-同步 public class Singleton { private static Singleton singleton; //加同步 public static synchronized Singleton getSingleton() { if (singleton == null) { singleton = new Singleton(); } return singleton; } public void print(){ System.out.println(singleton.hashCode()); } }
饿汉式:
饿汉现在有很多种写法,饿汉的实例是基于JVM在ClassLoader时创建的。ClassLoader在类加载时会验证静态代码块,基于此可以做实例化操作等
如过写饿汉的话 这样写,javadoc也是这么推荐的:
public class Singleton { private final static Singleton singleton= new Singleton(); private Singleton (){} public static Singleton getSingleton() { return singleton; } }
不过现在最常用的写法
比较常用的写法0-双重校验写法:(此种方法只能用在JDK5及以后版本(注意 INSTANCE 被声明为 volatile)
//双重检验 public class Singleton {
private volatile static Singleton singleton; private Singleton() { } //如果多线程情况下 public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { //多线程进入 线程1 进入此处 并有锁,此时未创建实例 另一个线程进入,无法获取锁,被阻塞 if (singleton == null) { //线程1 进入此处并创建实例 singleton = new Singleton(); } }//线程1创建实例并释放锁 ,然后线程2进入synchronized同步快,发现已经创建了实例 } return singleton; } }