答案
//饿汉式单列
public class HungryDemo1 {
private HungryDemo1() {
}
private final static HungryDemo1 HungryDemo1 = new HungryDemo1();
public static HungryDemo1 getInstance(){
return HungryDemo1;
}
}
//懒汉式单例
public class LazyManDemo1 {
private LazyManDemo1() {
}
private static LazyManDemo1 LazyManDemo1;
public static LazyManDemo1 getInstance(){
if (LazyManDemo1 == null){
LazyManDemo1 = new LazyManDemo1();
}
return LazyManDemo1;
}
}
在单线程中,以上的懒汉式代码是不会出现问题的,但是如果在多线程的并发环境中,还能保证代码的正确性吗?让我们来试验一下:
//懒汉式单例(多线程场景)
public class LazyManDemo2 {
private LazyManDemo2() {
System.out.println(Thread.currentThread().getName()+" - > 创建了单例对象");
}
private static LazyManDemo2 LazyMan;
public static LazyManDemo2 getInstance(){
if (LazyMan == null){
LazyMan = new LazyManDemo2();
}
return LazyMan;
}
public static void main(String[] args) {
for (int j = 0; j < 10; j++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
运行结果:
可以看到,在多线程模式下,LazyManDemo2的3次运行结果中,有两次都创建了多个实例对象,让单例模式不再单例。
原因:
//懒汉式单例 双重检测锁模式
public class LazyManDemo2 {
private LazyManDemo2() {
System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
}
private static LazyManDemo2 LazyMan;
public static LazyManDemo2 getInstance() {
if (LazyMan == null) {
synchronized (LazyManDemo2.class) {
if (LazyMan == null) {
LazyMan = new LazyManDemo2();
}
}
}
return LazyMan;
}
public static void main(String[] args) {
for (int j = 0; j < 10; j++) {
new Thread(() -> {
LazyManDemo2.getInstance();
}).start();
}
}
}
为了防止new LazyManDemo2()被执行多次,因此在实例化操作之前加上Synchronized 同步锁,**注意:**当synchronized()作用于静态方法时,就不再用this当锁对象,因为静态属性或方法不需要new出属性访问,所以就没有this引用的存在。
进入Synchronized 临界区以后,又进行了一次判断。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。
那这样构建出的单例模式是不是就绝对安全了呢?
首先来看一下new LazyManDemo2() 底层JVM的创建过程:
1.分配内存空间
2.执行构造方法,初始化对象
3.将对象指向刚分配的内存空间
现在模拟一个场景:A、B两个线程,A线程先访问了getInstance()方法,当A线程正在构建LazyMan对象时,B线程此时进入getInstance()方法,
在对象的创建过程中,如果A线程执行顺序是按照132执行,A线程正在执行步骤3且未完成执行状态,B线程也开始执行getInstance()方法,此时的LazyMan对象已经不在指向null,所以当B线程抢占到CPU资源,就会执行 if(instance == null)且返回结果为false, 直接拿着未初始化的LazyMan对象直接return结束掉方法,返回一个未构造完成LazyMan对象,导致问题的出现。这个问题改如何解决呢?
使用volatile,保证创建对象的过程是一个原子性操作,阻止了变量访问前后的指令重拍,保证指令的执行顺序,解决了DLC失效问题。
private volatile static LazyManDemo2 LazyMan;
public class LazyManDemo3 {
private LazyManDemo3() {
System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
}
private volatile static LazyManDemo3 LazyMan;
public static LazyManDemo3 getInstance() {
if (LazyMan == null) {
synchronized (LazyManDemo3.class) {
if (LazyMan == null) {
LazyMan = new LazyManDemo3();
}
}
}
return LazyMan;
}
public static void main(String[] args) {
for (int j = 0; j < 10; j++) {
new Thread(() -> {
LazyManDemo3.getInstance();
}).start();
}
}
}
//静态内部类
public class HolderSingle {
private HolderSingle() {
System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
}
public static HolderSingle getInstance(){
return InnerClass.LazyMan;
}
private static class InnerClass{
private static final HolderSingle LazyMan = new HolderSingle();
}
}
public class DestroySingle {
private DestroySingle() {
System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
}
private static volatile DestroySingle LazyMan;
public static DestroySingle getInstance() {
if (LazyMan == null) {
synchronized (DestroySingle.class) {
if (LazyMan == null) {
LazyMan = new DestroySingle();
}
}
}
return LazyMan;
}
public static void main(String[] args) throws Exception {
//通过单例获取实例
DestroySingle instance1 = DestroySingle.getInstance();
//通过反射获取实例
Constructor<DestroySingle> declaredConstructor = DestroySingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
DestroySingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println("instance1 == instance2:"+(instance1 == instance2));
}
}
/*================= 输出结果================
main - > 创建了单例对象
main - > 创建了单例对象
com.inspur.vista.ylt.conver.api.single.LazyManDemo4@77a567e1
com.inspur.vista.ylt.conver.api.single.LazyManDemo4@736e9adb
instance1 == instance2:false
================= 输出结果================*/
private DestroySingle() {
synchronized (DestroySingle.class){
if (LazyMan!= null){
throw new RuntimeException("单例对象,拒绝反射破坏操作!!");
}
System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
}
}
/*================= 输出结果================
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 org.javaboy.vhr.controller.system.DestroySingle.main(DestroySingle.java:32)
Caused by: java.lang.RuntimeException: 单例对象,拒绝反射破坏操作!!
at org.javaboy.vhr.controller.system.DestroySingle.(DestroySingle.java:8)
... 5 more
================= 输出结果================*/
在初次创建单例实例时就使用反射创建:
public class DestroySingle {
private DestroySingle() {
if (LazyMan!= null){
throw new RuntimeException("单例对象,拒绝反射破坏操作!!");
}
System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
}
private static volatile DestroySingle LazyMan;
public static DestroySingle getInstance() {
if (LazyMan == null) {
synchronized (DestroySingle.class) {
if (LazyMan == null) {
LazyMan = new DestroySingle();
}
}
}
return LazyMan;
}
public static void main(String[] args) throws Exception {
Constructor<DestroySingle> declaredConstructor = DestroySingle.class.getDeclaredConstructor(null);
//通过单例获取实例
DestroySingle instance1 = declaredConstructor.newInstance();
//通过反射获取实例
declaredConstructor.setAccessible(true);
DestroySingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println("instance1 == instance2:"+(instance1 == instance2));
}
}
/*================= 输出结果================
main - > 创建了单例对象
main - > 创建了单例对象
org.javaboy.vhr.controller.system.DestroySingle@7cca494b
org.javaboy.vhr.controller.system.DestroySingle@7ba4f24f
instance1 == instance2:false
================= 输出结果================*/
从结果可以看出单例类又获取了两个实例对象,通过反射再次成功的破坏了单例模式;
通过声明标志变量,在单例类第一次实例化时就进行标志更改
public class DestroySingle {
private static boolean flag = false;
private DestroySingle() {
if (flag == false){
flag = true;
}else{
throw new RuntimeException("单例对象,拒绝反射破坏操作!!");
}
System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
}
private static volatile DestroySingle LazyMan;
public static DestroySingle getInstance() {
if (LazyMan == null) {
synchronized (DestroySingle.class) {
if (LazyMan == null) {
LazyMan = new DestroySingle();
}
}
}
return LazyMan;
}
public static void main(String[] args) throws Exception {
Constructor<DestroySingle> declaredConstructor = DestroySingle.class.getDeclaredConstructor(null);
//通过单例获取实例
DestroySingle instance1 = declaredConstructor.newInstance();
//通过反射获取实例
declaredConstructor.setAccessible(true);
DestroySingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println("instance1 == instance2:"+(instance1 == instance2));
}
}
/*================= 输出结果================
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 org.javaboy.vhr.controller.system.DestroySingle.main(DestroySingle.java:37)
Caused by: java.lang.RuntimeException: 单例对象,拒绝反射破坏操作!!
at org.javaboy.vhr.controller.system.DestroySingle.(DestroySingle.java:13)
... 5 more
================= 输出结果================*/
运行结果第一个实例成功创建,创建第二个实例则创建失败;
当设置的标志变量被解密获取到:
public class DestroySingle {
private static boolean flag = false;
private DestroySingle() {
if (flag == false){
flag = true;
}else{
throw new RuntimeException("单例对象,拒绝反射破坏操作!!");
}
System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
}
private static volatile DestroySingle LazyMan;
public static DestroySingle getInstance() {
if (LazyMan == null) {
synchronized (DestroySingle.class) {
if (LazyMan == null) {
LazyMan = new DestroySingle();
}
}
}
return LazyMan;
}
public static void main(String[] args) throws Exception {
Constructor<DestroySingle> declaredConstructor = DestroySingle.class.getDeclaredConstructor(null);
Field flag = DestroySingle.class.getDeclaredField("flag");
declaredConstructor.setAccessible(true);
//通过单例获取实例
DestroySingle instance1 = declaredConstructor.newInstance();
flag.set(instance1,false);
//通过反射获取实例
DestroySingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println("instance1 == instance2:"+(instance1 == instance2));
}
}
/*================= 输出结果================
main - > 创建了单例对象
main - > 创建了单例对象
org.javaboy.vhr.controller.system.DestroySingle@7ba4f24f
org.javaboy.vhr.controller.system.DestroySingle@3b9a45b3
instance1 == instance2:false
================= 输出结果================*/
经过升级Plus的破坏操作后,单例模式再次创建了两个实例对象,破坏成功;
public class HolderSingle {
private HolderSingle() {
System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
}
public static HolderSingle getInstance(){
return InnerClass.LazyMan;
}
private static class InnerClass{
private static final HolderSingle LazyMan = new HolderSingle();
}
public static void main(String[] args) throws Exception {
//通过静态单例获取实例
HolderSingle instance1 = HolderSingle.getInstance();
//通过反射获取实例
Constructor<HolderSingle> declaredConstructor = HolderSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
HolderSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println("instance1 == instance2:"+(instance1 == instance2));
}
}
/*================= 输出结果================
main - > 创建了单例对象
main - > 创建了单例对象
org.javaboy.vhr.controller.system.HolderSingle@7cca494b
org.javaboy.vhr.controller.system.HolderSingle@7ba4f24f
instance1 == instance2:false
================= 输出结果================*/
以上的所谓单例模式都被一一破坏,接下来看看真正的单例模式。
public enum EnumSingle {
INSTANCE;
public static EnumSingle getInstance(){
return INSTANCE;
}
}
class EnumTest{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.getInstance();
EnumSingle instance2 = EnumSingle.getInstance();
System.out.println("instance1 == instance2:"+(instance1 == instance2));
System.out.println("=================================分割线=====================================");
//用反射来试一下
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance3 = declaredConstructor.newInstance();
EnumSingle instance4 = declaredConstructor.newInstance();
System.out.println("instance3 == instance4:"+(instance3 == instance4));
}
}
/*================= 输出结果================
instance1 == instance2:true
=================================分割线=====================================
Exception in thread "main" java.lang.NoSuchMethodException: com.inspur.vista.ylt.conver.api.single.EnumSingle.()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.inspur.vista.ylt.conver.api.single.EnumTest.main(EnumSingle.java:28)
================= 输出结果================*/
没有无参构造方法?进入到EnumSingle.java的所在文件目录,javap反编译看看结果:
编译结果可以看到,EnumSingle类中是存在着一个无参构造函数的,但是为什么在进行反射调用时却没找到呢?
那就用jad.exe1生成EnumSingle源码来看看是什么原因
既然知道了构造参数的参数类型,那就按照要求在试试:
public enum EnumSingle {
INSTANCE;
public static EnumSingle getInstance(){
return INSTANCE;
}
}
class EnumTest{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.getInstance();
EnumSingle instance2 = EnumSingle.getInstance();
System.out.println("instance1 == instance2:"+(instance1 == instance2));
System.out.println("=================================分割线=====================================");
//用反射来试一下
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance3 = declaredConstructor.newInstance();
EnumSingle instance4 = declaredConstructor.newInstance();
System.out.println("instance3 == instance4:"+(instance3 == instance4));
}
}
/*================= 输出结果================
instance1 == instance2:true
=================================分割线=====================================
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at cn.zx.single.EnumTest.main(EnumSingle.java:28)
================= 输出结果================*/
运行结果抛出了java.lang.IllegalArgumentException: Cannot reflectively create enum objects,这正是Constructor.newInstance()源码中的展现出来的代码,证明了不能通过反射创建枚举对象(破案,破案!),防守成功!
如果觉得文章不错,就给点个赞吧!
一个反编译工具,通过反编译将.class转成.java文件源码 ↩︎