单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例。
Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
那么如何实现一个单例呢?单例的实现方式有饿汉,懒汉式。懒汉也好,饿汉也好,实现单例的要点就在于让构造方法私有,然后提供公有的静态的方法,对外返回单例的对象实例。懒汉和饿汉的区别在于创建单例对象的时机,一个在类加载时就创建了,一个在调用newInstence方法时才创建。所以,饿汉时实现更为简单,不需要考虑线程安全,但是懒汉式要考虑线程的安全问题。
饿汉式:
public class CatHModel { private String name; private static final CatHModel cat = new CatHModel("Tom"); private CatHModel(String name) { this.name = name; } public String getName() { return this.name; } public static CatHModel newInstence(String name) { return cat; } }
final确保了私有变量cat永远指向一开始创建的名为“Tom"的对象。构造方法私有,无法通过new的方式创建对象。
CatHModel cat = CatHModel.newInstence(); CatHModel cat1 = CatHModel.newInstence(); System.out.println(cat == cat1);
输出为
true
Process finished with exit code 0
可见得到的两个对象地址一致,也就是实际指向的是同一个对象。
懒汉式(线程安全)
public class CatFModel { private String name; private static CatFModel cat; private CatFModel() { } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public static CatFModel newInstence() { if (cat == null) { synchronized (CatFModel.class) { if (cat == null) { cat = new CatFModel(); } } } return cat; } }
我们来测试一下
List<CatFModel> list = Collections.synchronizedList(new ArrayList<>()); int size = 5; CountDownLatch countDownLatch = new CountDownLatch(size); for (int i = 0; i < size; i++) { final int temp = i; new Thread(() -> { String name = "Sam" + temp; CatFModel catFModel = CatFModel.newInstence(); catFModel.setName(name); list.add(catFModel); countDownLatch.countDown(); }).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } list.forEach(e -> System.out.println(e.getName()));
输出为:
Sam1
Sam1
Sam1
Sam1
Sam1
Process finished with exit code 0
打印的是最后一个线程set的name,因为多个线程使用的是同一个实例,所以最后得到的名字是一样的。多线程下能够保证安全。
但是以上方式实现的单例有一个问题,就是如果通过反射的方式是能够调用私有构造方法创建新对象的。
CatHModel cat = CatHModel.newInstence(); System.out.println(cat.getName()); Constructor constructor = CatHModel.class.getDeclaredConstructor(String.class); constructor.setAccessible(true); CatHModel cat2 = (CatHModel) constructor.newInstance("Henry"); System.out.println(cat2.getName());
输出为:
Tom
Henry
Process finished with exit code 0
通过枚举实现单例模式:
public enum CatEnum { INSTANCE; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public static CatEnum getInstance(){ return INSTANCE; } }
再来试一下
List<CatEnum> list = Collections.synchronizedList(new ArrayList<>()); int size = 5; CountDownLatch countDownLatch = new CountDownLatch(size); for (int i = 0; i < size; i++) { final int temp = i; new Thread( () -> { String name = "Sam" + temp; CatEnum catEnum = CatEnum.getInstance(); catEnum.setName(name); list.add(catEnum); countDownLatch.countDown(); } ).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } list.forEach(e -> System.out.println(e.getName()));
输出为
Sam4
Sam4
Sam4
Sam4
Sam4
Process finished with exit code 0
那我们再试一下
Constructor constructor = CatEnum.class.getDeclaredConstructor(String.class,int.class); constructor.setAccessible(true); CatEnum cat2 = (CatEnum) constructor.newInstance(); System.out.println(cat2.getName());
输出
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.base/java.lang.reflect.Constructor.acquireConstructorAccessor(Constructor.java:544)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:496)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:483)
at java_problems.day3.test.main(test.java:67)
会报错,我们看一下具体代码
代码到这里就点不进去了,但是我们看到上面的注释,保证反射的类不是一个枚举类,所以JAVA底层帮助我们做了限制,使得我们无法通过反射的方式破坏单例。