/** * 懒汉式单例1 * 事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效 * 没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton1 实例 */ public class Singletion1 { //第一步先将构造方法私有化 private Singletion1(){} // 然后声明一个静态变量保存单例的引用 private static Singletion1 singletion1 = null; //通过提供一个静态方法来获得单例的引用 public static Singletion1 getInstance(){ if (singletion1==null){ singletion1 = new Singletion1(); } return singletion1; } }
测试:
public class ExectorThread implements Runnable { @Override public void run() { LazySimpleSingleton singleton = LazySimpleSingleton.getInstance(); System.out.println(Thread.currentThread().getName()+":"+singleton); } }
public class LazySimpleSingletonTest { public static void main(String[] args){ Thread t1 = new Thread(new ExectorThread()); Thread t2 = new Thread(new ExectorThread()); t1.start(); t2.start(); System.out.println("end"); //end //Thread-0:com.vip.singleton.lazy.LazySimpleSingleton@c6dfc63 //Thread-1:com.vip.singleton.lazy.LazySimpleSingleton@29b4bc88 // 一定几率出现创建两个不同结果的情况,意味着上面的单例存在线程安全隐患 } }
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式
懒汉式单例的特点是:被外部类调用的时候内部类才会加载
/** *懒汉式单例.保证线程安全 * */ public class Singletion2 { private Singletion2() {} private static Singletion2 singleton2 = null; //为了保证多线程环境下正确访问,给方法加上同步锁synchronized //虽然线程安全了,但是每次都要同步,会影响性能 // public static synchronized Singletion2 getInstance(){ // if (singleton2==null){ // singleton2 = new Singletion2(); // } // return singleton2; // } //为了保证多线程环境下的另一种实现方式,双重锁检查 //做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
//双重检查锁的单例模式
public static Singletion2 getInstance(){ if (singleton2 ==null){ synchronized (Singletion2.class){ if (singleton2==null){ singleton2 = new Singletion2(); } } } return singleton2; } }
方法加上同步锁synchronized调试:
当我们将其中一个线程执行并调用 getInstance()方法时,另一 个线程在调用 getInstance()方法,线程的状态由 RUNNING 变成了 MONITOR,出现阻 塞。直到第一个线程执行完,第二个线程才恢复 RUNNING 状态继续调用 getInstance() 方法
/** *线程安全问题 */ public class Singletion2Test { String name = null; private Singletion2Test(){} //volatile关键字来声明单例对象 //方便编写多线程程序和利于编译器进行优化 private static volatile Singletion2Test singletion2Test = null; public static Singletion2Test getInstance(){ if (singletion2Test == null){ synchronized (Singletion2Test.class){ if (singletion2Test == null){ singletion2Test = new Singletion2Test(); } } } return singletion2Test; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void printInfo(){ System.out.println(name); } public static void main(String[] args){ Singletion2Test instance = Singletion2Test.getInstance(); instance.setName("abc"); Singletion2Test instance2 = Singletion2Test.getInstance(); instance.setName("def"); instance.printInfo(); instance2.printInfo(); System.out.println(instance==instance2); } }
/** * 静态内部类 * 兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题 ,完美地屏蔽了这两个缺点 */ public class LazyInnerClassSingleton { //默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类 //如果没使用的话,内部类是不加载的 private LazyInnerClassSingleton(){} //static 是为了使单例的空间共享 //保证这个方法不会被重写,重载 public static final LazyInnerClassSingleton getInstance(){ //在返回结果以前,一定会先加载内部类 return LazyHolder.LAZY; } //默认不加载 private static class LazyHolder{ private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
饿汉式单例:
饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存
/** *饿汉式单例 * 在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的 */ public class Singletion4 { private Singletion4(){} //声明静态变量,在类实例化之前就初始化变量,将对象引用保存 private static final Singletion4 SINGLETION = new Singletion4(); public static Singletion4 getInstance(){ return SINGLETION; } }
//饿汉式静态块单例 public class HungryStaticSingleton { private HungryStaticSingleton(){} private static HungryStaticSingleton hungryStaticSingleton= null; static { hungryStaticSingleton = new HungryStaticSingleton(); } public static HungryStaticSingleton getInstance(){ return hungryStaticSingleton; } }
/** * 饿汉式 * 枚举式单例 *不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象 */ import java.io.Serializable; public enum EnumSingleton implements Serializable { Instance; public static EnumSingleton getInstance(){ return Instance; } }
import java.util.HashMap; import java.util.Map; /** *登记式单例 * 内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了 * 维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回 */ public class Singletion6 { private static Map<String,Singletion6> map = new HashMap<>(); static { Singletion6 singletion6 = new Singletion6(); map.put(singletion6.getClass().getName(),singletion6); } protected Singletion6(){} public static Singletion6 getInstance(String name){ if (name==null){ name = Singletion6.class.getName(); } if (map.get(name)==null){ try { map.put(name, (Singletion6) Class.forName(name).newInstance()); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return map.get(name); } }
import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; /** *CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行 * * 与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。 * * 其他N 个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象, * 他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的; * 每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0, * 然后主线程就能通过await()方法,恢复执行自己的任务。 */ public class Test { public static void main(String[] args){ CountDownLatch latch = new CountDownLatch(100); Set<Singletion3> synchronizedSet = Collections.synchronizedSet(new HashSet<>()); for (int a=0;a<100;a++){ new Thread(){ @Override public void run() { synchronizedSet.add(Singletion3.getInstance()); } }.start(); latch.countDown(); } try { latch.await();//等待所有线程全部完成,最终输出结果 System.out.println(synchronizedSet.size());//1 } catch (InterruptedException e) { e.printStackTrace(); } } }
防止反序列化方式二(方式一请看枚举):
import java.io.Serializable; /** * 序列化会通过反射调用无参数的构造方法创建一个新的对象 */ public class SerSingleton implements Serializable { String name; public SerSingleton() { System.out.println("Singleton is create"); name = "SerSingleton"; } private static SerSingleton instance = new SerSingleton(); public static SerSingleton getInstance(){ return instance; } public static void createString(){ System.out.println("createString in Singleton"); } //java.io.ObjectInputStream.readOrdinaryObject 方法中的invokeReadResolve会通过反射的方式调用要被反序列化的类的readResolve方法 //在反序列化后,去掉会生成多个对象实例 private Object readResolve(){ //阻止生成新的实例,总是返回当前对象 return instance; } } //测试 import com.mod.SerSingleton; import java.io.*; public class SerSingletonTest { public static void main(String[] args) throws Exception { SerSingleton s1 = null; SerSingleton s = SerSingleton.getInstance(); FileOutputStream fos = new FileOutputStream("SerSingleton.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("SerSingleton.txt"); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (SerSingleton) ois.readObject(); //判断是否是同一个对象 System.out.println(s1==s); } }
readResolve原理分析:
JDK 的源码->ObjectInputStream 类的 readObject()方法
在 readObject 中又调用了我们重写的 readObject0()方法。进入 readObject0() 方法,代码如下:
case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));
调用了 ObjectInputStream 的 readOrdinaryObject() 方法,我们继续进入看源码:
readResolveMethod = getInheritableMethod( cl, "readResolve", null, Object.class);
通过 JDK 源码分析我们可以看出,虽然,增加 readResolve()方法返回实例,解决了单 例被破坏的问题。但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两 次,只不过新创建的对象没有被返回而已。那如果,创建对象的动作发生频率增大,就 意味着内存分配开销也就随之增大
防止反射破坏单例:
方式一:构造器判断
public class StaticSingleton{ private StaticSingleton() { System.out.println("StaticSingleton is create"); if (SingletonHolder.instance!=null){ throw new IllegalStateException(); } } private static class SingletonHolder{ //构造器判断,防止反射攻击 private static StaticSingleton instance = new StaticSingleton(); } public static final StaticSingleton getInstance(){ return SingletonHolder.instance; } } import java.lang.reflect.Constructor; /** * 反射测试 */ public class SerSingletonTest2 { public static void main(String[] args) throws Exception { Class clazz = StaticSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); StaticSingleton instance = (StaticSingleton) constructor.newInstance(); StaticSingleton instance2 = StaticSingleton.getInstance(); System.out.println(instance==instance2); } }
方式二:枚举
/** * 反射测试 */ public class SerSingletonTest2 { public static void main(String[] args) throws Exception { Class clazz = EnumSingleton.class;//枚举单例 Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); EnumSingleton instance = (EnumSingleton) constructor.newInstance(); EnumSingleton instance2 = EnumSingleton.getInstance(); System.out.println(instance==instance2); } //Exception in thread "main" java.lang.NoSuchMethodException: com.mod.EnumSingleton.<init>() // at java.lang.Class.getConstructor0(Class.java:3082) // at java.lang.Class.getDeclaredConstructor(Class.java:2178) // at com.Test.SerSingletonTest2.main(SerSingletonTest2.java:15) }
注册式单例:
注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记
在 JDK 枚举的语法特殊性,以及反射也为枚举保 驾护航,让枚举式单例成为一种比较优雅的实现
public enum EnumSingletion { INTERFACE; private Object data; public static EnumSingletion getInstance(){ return INTERFACE; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }
测试:
public class SerializableSingletionTest { public static void main(String[] args){ // HungrySingleton s1 = null; // HungrySingleton s2 = HungrySingleton.getInstance(); EnumSingletion s1 = null; EnumSingletion s2 = EnumSingletion.getInstance(); s2.setData("1"); FileOutputStream fos = null; try { fos = new FileOutputStream("SeriableSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("SeriableSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); // s1 = (HungrySingleton)ois.readObject(); s1 = (EnumSingletion)ois.readObject(); ois.close(); System.out.println(s1.getData()); System.out.println(s2.getData()); System.out.println(s1.getData() == s2.getData()); }catch (Exception e){ e.printStackTrace(); } } }
容器缓存的写法:
//注册式单例 -- 容器缓存的写法 public class ContainerSingleton { private ContainerSingleton(){} private static Map<String,Object> map = new ConcurrentHashMap<>(); public static Object getBean(String className){ if (!map.containsKey(className)){ Object object = null; try { object = Class.forName(className).newInstance(); map.put(className,object); }catch (Exception e){ e.printStackTrace(); } return object; }else { return map.get(className); } } }
容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的
ThreadLocal 线程单例:
//ThreadLocal 线程单例 public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> instance = new ThreadLocal<ThreadLocalSingleton>(){ @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private ThreadLocalSingleton(){} public static ThreadLocalSingleton getInstance(){ return instance.get(); } }
测试:
public class ExectorThread implements Runnable { @Override public void run() { ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance(); System.out.println(Thread.currentThread().getName()+":"+singleton); } }
public class ThreadLocalSingletonTest { public static void main(String[] args){ System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); Thread t1 = new Thread(new ExectorThread()); Thread t2 = new Thread(new ExectorThread()); t1.start(); t2.start(); System.out.println("end"); //com.vip.singleton.ThreadLocalSingleton@4b67cf4d //com.vip.singleton.ThreadLocalSingleton@4b67cf4d //com.vip.singleton.ThreadLocalSingleton@4b67cf4d //com.vip.singleton.ThreadLocalSingleton@4b67cf4d //Thread-0:com.vip.singleton.ThreadLocalSingleton@5a67a4e //end //Thread-1:com.vip.singleton.ThreadLocalSingleton@17141277 } }
ThreadLocal 不能保证其 创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全
ThreadLocal 将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以 空间换时间来实现线程间隔离的。