饿汉式:饿汉式,一开始就把对象创建好,然后返回对象给调用方法的人,缺点浪费空间,优点,一开始用final写死了对象,对象怎么样都不会出现新的。
package single;
public class Hungry {
private Hungry() {
}
private final static Hungry HUNGRY =new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(()->{
Hungry.getInstance();
}).start();
}
}
}
//饿汉式,一开始就把对象创建好,然后返回对象给调用方法的人
懒汉式:等需求的时候再创建,这个比较简单,就是调用getInstance的时候,判断定义里的对象是否为空,不为空就返回一个新对象。优点:需求的时候才创建,节省空间。缺点:多线程下会出现问题。
package single;
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan() {
System.out.println(Thread.currentThread().getName());
}
public static LazyMan getInstance(){
if (lazyMan==null)
{
lazyMan=new LazyMan();
System.out.println(lazyMan);
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
结果:多线程下出现了这样的结果,说明这并不符合单例这个条件
Thread-0
Thread-1
single.LazyMan@5eefc31e
single.LazyMan@16187bcd
、
DCL懒汉式:在获取对象的时候,加上判断,如果lazyMan为空,则加上类锁,这样就防止多个线程同时去创建lazyMan。但是还是有缺陷,如果我们此时使用反射获取对象,则会出现问题。
private volatile static LazyMan lazyMan;
//由于创造对象是分成了三步所以必须加上volatile来防止指令重排
//1、分配内存空间
//2、执行构造方法
//3、返回对象
//如果执行的123则没问题,但就怕执行132则出错
public static LazyMan getInstance(){
if (lazyMan==null)
{ synchronized (LazyMan.class){
if (lazyMan==null)
{
lazyMan=new LazyMan();
System.out.println(lazyMan);
}
}
}
return lazyMan;
}
正常获取:
Thread-0
single.LazyMan@469dad33
使用反射获取:
package single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class LazyMan {
private LazyMan() {
System.out.println(Thread.currentThread().getName());
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null)
{ synchronized (LazyMan.class){
if (lazyMan==null)
{
lazyMan=new LazyMan();
System.out.println(lazyMan);
}
}
}
return lazyMan;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true); //取消私有化
LazyMan lazyMan1 = constructor.newInstance();
LazyMan lazyMan2 = constructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);
}
}
输出结果:
main
main
single.LazyMan@14ae5a5
single.LazyMan@7f31245a
说明反射的确可以处理这个问题,但是还有办法再防止这种反射,就是设置标志位,但是防止不了反编译:
package single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class LazyMan {
private static boolean zsp =false;
private LazyMan() {
if (zsp==false)
{
zsp=true;
}
else
{
throw new RuntimeException("不要试图用反射来破坏单例模式");
}
System.out.println(Thread.currentThread().getName());
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null)
{ synchronized (LazyMan.class){
if (lazyMan==null)
{
lazyMan=new LazyMan();
System.out.println(lazyMan);
}
}
}
return lazyMan;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true); //取消私有化
LazyMan lazyMan1 = constructor.newInstance();
LazyMan lazyMan2 = constructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);
}
}
说明的确是可以的,但是无法防止对方反编译去获取我们的标志位从而使用反射改变标志位和反射结果:
main
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at single.LazyMan.main(LazyMan.java:42)
Caused by: java.lang.RuntimeException: 不要试图用反射来破坏单例模式
at single.LazyMan.<init>(LazyMan.java:15)
... 5 more
Process finished with exit code 1
这里我们就需要引入枚举:
package single;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) {
EnumSingle enumSingle1 =EnumSingle.INSTANCE;
EnumSingle enumSingle2 =EnumSingle.INSTANCE;
System.out.println(enumSingle1);
System.out.println(enumSingle2);
}
}
输出出来变成:
INSTANCE
INSTANCE
使用反射来破坏,发现错误,构造方法并不是空参:
package single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle enumSingle1 =EnumSingle.INSTANCE;
EnumSingle enumSingle2 =EnumSingle.INSTANCE;
System.out.println(enumSingle1);
System.out.println(enumSingle2);
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
EnumSingle enumSingle = constructor.newInstance();
System.out.println(enumSingle);
}
}
但是报的错误并不是newInstance里的错误Cannot reflectively create enum objects,而是没有空参构造
INSTANCE
INSTANCE
Exception in thread "main" java.lang.NoSuchMethodException: single.EnumSingle.<init>()
使用jad查看后发现:
并么有空参构造,而是String,int
修改后发现:
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
INSTANCE
INSTANCE
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at single.Test.main(EnumSingle.java:22)
最后证明了枚举可以很好的解决反射去调用单例模式的问题。