单例模式
饿汉式单例模式:
package com.zyc.playSingle;
//饿汉式单例模式
public class hungryMan {
//私有构造器,这样就不能通过构造器创建实例了
private hungryMan(){
}
private static final hungryMan HUNGR_MAN = new hungryMan();
//只要有就给:饿汉的特点,使用static这样可以直接用类访问
public static hungryMan getHungrMan(){
return HUNGR_MAN;
}
}
class t1{
public static void main(String[] args) {
hungryMan hungrMan = com.zyc.playSingle.hungryMan.getHungrMan();
hungryMan hungrMan1 = hungryMan.getHungrMan();
hungryMan hungrMan2 = hungryMan.getHungrMan();
System.out.println(hungrMan);
System.out.println(hungrMan1);
System.out.println(hungrMan2);
}
}
懒汉式单例模式:
package com.zyc.playSingle;
//懒汉式单例模式
public class lazyMan {
private lazyMan(){
}
private static lazyMan LAZY_MAN ;
public static lazyMan getLAZY_MAN(){
if (LAZY_MAN==null){
LAZY_MAN= new lazyMan();
return LAZY_MAN;
}
return LAZY_MAN;
}
}
class t2{
public static void main(String[] args) {
lazyMan lazy_man = lazyMan.getLAZY_MAN();
lazyMan lazy_man1 = lazyMan.getLAZY_MAN();
System.out.println(lazy_man);
System.out.println(lazy_man1);
}
}
单线程情况下我们使用单例模式是安全的,那么多线程呢?
package com.zyc.playSingle;
public class DCLLazyMan {
private DCLLazyMan(){
System.out.println(Thread.currentThread().getName()+"ing...."); //测试 每次打印创建单例的线程名
}
private static DCLLazyMan dclLazyMan;
//懒汉模式
public static DCLLazyMan getDclLazyMan(){
if(dclLazyMan==null){
dclLazyMan = new DCLLazyMan();
}
return dclLazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
DCLLazyMan.getDclLazyMan();
}).start();
}
}
}
这里我们使用10个线程创建单例,结果如下:
我们看到结果不同,所以多线程情况下这个是线程不安全的
解决办法:加锁
//双重检测锁模式 DCL懒汉式
public static DCLLazyMan getDclLazyMan(){
if (dclLazyMan==null){
synchronized (DCLLazyMan.class){
if(dclLazyMan==null){
dclLazyMan = new DCLLazyMan();
}
}
}
return dclLazyMan;
}
使用双重检测锁我们看到结果如下:
结果现在满足了我们的需求,那么已经完全保证线程问题了吗?
指令重排
原因:创建对象dclLazyMan = new DCLLazyMan();这行代码不是一个原子性操作,可能会造成指令重排(1.分配内存空间2.初始化对象3.对象指向内存空间//这三步的顺序会重新排序,有可能先进行3操作在进行2)
所以我们需要使用volatile避免指令重排
更改后代码如下:
package com.zyc.playSingle;
public class DCLLazyMan {
private DCLLazyMan(){
System.out.println(Thread.currentThread().getName()+"ing....");
}
private volatile static DCLLazyMan dclLazyMan;
//双重检测锁模式 DCL懒汉式
public static DCLLazyMan getDclLazyMan(){
if (dclLazyMan==null){
synchronized (DCLLazyMan.class){
if(dclLazyMan==null){
dclLazyMan = new DCLLazyMan();
}
}
}
return dclLazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
DCLLazyMan.getDclLazyMan();
}).start();
}
}
}
使用反射破解单例模式
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
DCLLazyMan dclLazyMan1 = DCLLazyMan.getDclLazyMan();
Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
DCLLazyMan dclLazyMan = declaredConstructor.newInstance();
System.out.println(dclLazyMan);
System.out.println(dclLazyMan1);
}
使用反射后可以发现创建两个对象不同;
解决办法,构造器进行判断
private static boolean flag = false;
private DCLLazyMan(){
synchronized (DCLLazyMan.class){
if (flag==false){
flag=true;
}
else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName()+"ing....");
}
但是我们上面是用反射创建了一个对象和原本创建的对象进行比较,如果全部使用反射创建对象呢?
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
DCLLazyMan dclLazyMan = declaredConstructor.newInstance();
DCLLazyMan dclLazyMan1 = declaredConstructor.newInstance();
System.out.println(dclLazyMan);
System.out.println(dclLazyMan1);
}
解决办法:使用标识符
private static boolean flag = false;
private DCLLazyMan(){
synchronized (DCLLazyMan.class){
if (flag==false){
flag=true;
}
else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName()+"ing....");
}
但是如果通过反编译或者其他手段获得我们设置的标识符呢?
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Field flag = DCLLazyMan.class.getDeclaredField("flag");
flag.setAccessible(true);
Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
DCLLazyMan dclLazyMan = declaredConstructor.newInstance();
flag.set(dclLazyMan,false);
DCLLazyMan dclLazyMan1 = declaredConstructor.newInstance();
System.out.println(dclLazyMan);
System.out.println(dclLazyMan1);
}
通过修改标识符我们单例模式又被破坏了
解决办法:通过源码分析使用枚举
我们下面使用枚举类型进行分析
创建枚举类然后利用反射创建对象:
package com.zyc.playSingle;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//enum 是什么? enum本身就是一个Class 类
public enum enumSingle {
INSTANCE;
public enumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
enumSingle instance1 = enumSingle.INSTANCE;
Constructor<enumSingle> declaredConstructor = enumSingle.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
enumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
报错如下:
我们发现结果跟源码中读的不同:
经过JAD工具进行反编译我们可以得到这里不是一个空参构造器,而是一个有参构造器
Constructor<enumSingle> declaredConstructor = enumSingle.class.getDeclaredConstructor(String.class,int.class);
修改后:
抛出异常正确,成功使用枚举类避免了反射的破坏