单例模式
单例模式基本分为两种:饿汉式和饱汉式
还有一些比较好玩的值得研究的场景。
饿汉式
package com.pihao.main.single;
/**
* 饿汉式
*/
public class HungryMan {
private HungryMan(){
}
private static HungryMan hungryMan = new HungryMan();
private static HungryMan getInstance(){
return hungryMan;
}
//思考:这种写法在该对象还没使用的时候就要先去加载庞大的资源,造成浪费
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
}
饱汉式/懒汉式
package com.pihao.main.single;
/**
* 饱汉式、懒汉式
*/
public class FullMan {
private FullMan(){
}
private static FullMan fullMan;
public static FullMan getInstance(){
if(fullMan == null){
//要用的时候再去加载new
fullMan = new FullMan();
}
return fullMan;
}
}
单例模式的安全性验证
package com.pihao.main.single;
/**
* 饱汉式、懒汉式
*/
public class FullMan {
private FullMan(){
System.out.println(Thread.currentThread().getName() + "启动了");
}
private static FullMan fullMan;
public static FullMan getInstance(){
if(fullMan == null){
//要用的时候再去加载
fullMan = new FullMan();
}
return fullMan;
}
//上述代码在单线程下没问题的,但是在多线程的场景下如果保证单例呢?
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
FullMan.getInstance();
}).start();
}
}
}
//执行效果如下:
Thread-1启动了
Thread-2启动了
Thread-0启动了
结论:在并发的情况下,懒汉式的单例模式是有问题的,不能保证获取到的是同一个对象。
解决办法:上锁
双重检测锁模式的懒汉式单例----DCL懒汉式
package com.pihao.main.single;
/**
* 饱汉式、懒汉式
*/
public class FullMan {
private FullMan(){
System.out.println(Thread.currentThread().getName() + "启动了");
}
//使用volatile关键字,防止指令重排
private volatile static FullMan fullMan;
public static FullMan getInstance(){
if(fullMan == null){
//加锁
synchronized (FullMan.class){
if(fullMan == null){
//要用的时候再去加载
fullMan = new FullMan();
}
}
}
return fullMan;
}
//上述代码在单线程下没问题的,但是在多线程的场景下如果保证单例呢?
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
FullMan.getInstance();
}).start();
}
}
}
//执行效果如下:
Thread-0启动了
结论:在常规并发的情况下,只能获取到同一个对象。解决了上述的问题
静态内部类
package com.pihao.main.single;
public class Outer {
private Outer(){
}
public static Outer getInstance(){
return InnerClass.OUTER;
}
public static class InnerClass{
private static final Outer OUTER = new Outer();
}
}
利用反射破坏单例
package com.pihao.main.single;
import java.lang.reflect.Constructor;
/**
* 饱汉式、懒汉式
*/
public class FullMan {
private FullMan(){
}
private volatile static FullMan fullMan;
public static FullMan getInstance(){
if(fullMan == null){
synchronized (FullMan.class){
if(fullMan == null){
//要用的时候再去加载
fullMan = new FullMan();
}
}
}
return fullMan;
}
//利用反射破坏单例
public static void main(String[] args) throws Exception {
FullMan instance1 = FullMan.getInstance();
//无参构造
Constructor<FullMan> constructor = FullMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
FullMan instance2 = constructor.newInstance();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
//输出为
1163157884
1956725890
说明创建了两个不同的对象,单例不安全,怎么解决呢?
//上述问题解决办法
package com.pihao.main.single;
import java.lang.reflect.Constructor;
/**
* 饱汗式、懒汉式
*/
public class FullMan {
private FullMan(){
synchronized (FullMan.class){
if(fullMan != null){
throw new RuntimeException("非法构建对象!");
}
}
}
private volatile static FullMan fullMan;
public static FullMan getInstance(){
if(fullMan == null){
synchronized (FullMan.class){
if(fullMan == null){
//要用的时候再去加载
fullMan = new FullMan();
}
}
}
return fullMan;
}
}
继续进阶
上述代码是一个new 对象,一个利用反射来创建的对象,那么如果我两个对象都是用反射来创建的呢?还会是同一个值吗?
package com.pihao.main.single;
import java.lang.reflect.Constructor;
/**
* 饱汗式、懒汉式
*/
public class FullMan {
private FullMan(){
synchronized (FullMan.class){
if(fullMan != null){
throw new RuntimeException("非法构建对象!");
}
}
}
private volatile static FullMan fullMan;
public static FullMan getInstance(){
if(fullMan == null){
synchronized (FullMan.class){
if(fullMan == null){
//要用的时候再去加载
fullMan = new FullMan();
}
}
}
return fullMan;
}
//利用反射破坏单例
public static void main(String[] args) throws Exception {
Constructor<FullMan> constructor = FullMan.class.getDeclaredConstructor(null); //无参构造
constructor.setAccessible(true);
FullMan instance1 = constructor.newInstance();
FullMan instance2 = constructor.newInstance();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
//输出为
1163157884
1956725890
发现又创建了两个不同的对象,怎么解决呢?可以添加一个属性,类似红绿灯的方式
package com.pihao.main.single;
import java.lang.reflect.Constructor;
/**
* 饱汗式、懒汉式
*/
public class FullMan {
private static boolean flag = false;
private FullMan(){
synchronized (FullMan.class){
if(flag == false){
flag = true;
}else{
//进来两次就报错
throw new RuntimeException("非法构建对象!");
}
}
}
private volatile static FullMan fullMan;
public static FullMan getInstance(){
if(fullMan == null){
synchronized (FullMan.class){
if(fullMan == null){
//要用的时候再去加载
fullMan = new FullMan();
}
}
}
return fullMan;
}
//利用反射破坏单例
public static void main(String[] args) throws Exception {
Constructor<FullMan> constructor = FullMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
FullMan instance1 = constructor.newInstance();
FullMan instance2 = constructor.newInstance();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
//使用上述的标志后确实可以解决多次反射创建对象的问题,但是。。。。。。
思考:在第一次使用反射创建完instance1对象之后,是否可以利用反射将属性flag 的值重新设置为false,那么之后又可以创建instance2对象了。。。道高一尺,魔高一丈!
那么最终的办法是怎么解决才能保证安全呢?
枚举!!
枚举本身也是一个class类
package com.pihao.main.single;
import java.lang.reflect.Constructor;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return EnumSingle.INSTANCE;
}
public static void main(String[] args)throws Exception {
//Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null);
//Exception in thread "main" java.lang.NoSuchMethodException: com.pihao.main.single.EnumSingle.<init>()
//上面的报错是因为底层没有空参构造这个方法,虽然看class类有这个方法,可以使用jad反编译一下
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
EnumSingle instance2 = constructor.newInstance();
//报错: Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
}
}
//结论:枚举类不会被反射破坏,使用枚举创建单例模式是安全的