程序猿学社的GitHub,欢迎Star
github技术专题
本文已记录到github
你问程序猿小哥哥,有对象吗
他会毫不犹豫的告诉你,有,简单呀,new一个呗。
我们知道"对象"只会有一个,毕竟,不是谁都是韦小宝,有7个老婆,可以new 7次。
那怎么保证只会new出一个对象,这就是本文的主角单例模式。
现在这年代太难了,找对象难,在程序中,new一个对象,还这么讲究。
采取一定的手段,保证一个类中只有一个实例,并且只提供一个取到该实例的入口。
总结:
如果该对象频繁的被使用,我们就可以考虑把他设计单例。
package com.cxyxs.designmode.util;
/**
* Description:
* Author: 程序猿学社
* Date: 2020/3/15 12:30
* Modified By:
*/
public class Demo1 {
public static void main(String[] args) {
Girlfriend gf = new Girlfriend();
Girlfriend gf1 = new Girlfriend();
System.out.println(gf == gf1);
}
}
class Girlfriend{
}
package com.cxyxs.designmode.util;
/**
* Description:
* Author: 程序猿学社
* Date: 2020/3/15 13:15
* Modified By:
*/
public class Demo2 {
public static void main(String[] args) {
Girlfriend2 gf1 = Girlfriend2.getInstance();
Girlfriend2 gf2 = Girlfriend2.getInstance();
System.out.println(gf1 == gf2);
}
}
class Girlfriend2{
//第二步
private static Girlfriend2 gf = new Girlfriend2();
// 第一步
private Girlfriend2() {
}
//第三步
public static Girlfriend2 getInstance(){
return gf;
}
}
输出的值为true,说明只实例化一次。我们来梳理一下代码实现思路。
在学习单例过程中,我们会经常看到一个词汇“懒加载”,他是什么意思?
总结:
饿汉式是通过static变量,也就是类加载原理保证单例,也就是所谓的线程安全。没有实现懒加载。
个人的建议:
可以使用,内存浪费的问题,几乎可以忽略,在实际开发过程中,存在一个类,也不调用,还傻傻的放在那里,说明这个代码,可能是无用的代码,对于,没有的代码,直接干掉。
package com.cxyxs.designmode.util;
/**
* Description:懒汉式
* Author: 程序猿学社
* Date: 2020/3/15 14:00
* Modified By:
*/
public class Demo3 {
public static void main(String[] args) {
Girlfriend3 gf = Girlfriend3.getInstance();
Girlfriend3 gf1 = Girlfriend3.getInstance();
System.out.println(gf == gf1);
}
}
class Girlfriend3{
private static Girlfriend3 gf = null;
private Girlfriend3() {
}
public static Girlfriend3 getInstance(){
if(gf == null){
gf = new Girlfriend3();
}
return gf;
};
}
打印的结果也为true,各位社友,是不是觉得,这就实现单例?
实际上,这种写法是有问题的,会存在线程安全的问题
package com.cxyxs.designmode.util;
import java.util.concurrent.TimeUnit;
/**
* Description:懒汉式,模拟线程不安全
* Author: 程序猿学社
* Date: 2020/3/15 14:00
* Modified By:
*/
public class Demo4 {
public static void main(String[] args) {
new Thread(()->{
Girlfriend4 gf = Girlfriend4.getInstance();
System.out.println(gf);
}).start();
new Thread(()->{
Girlfriend4 gf1 = Girlfriend4.getInstance();
System.out.println(gf1);
}).start();
}
}
class Girlfriend4{
private static Girlfriend4 gf = null;
private Girlfriend4() {
}
public static Girlfriend4 getInstance(){
if(gf == null){
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
gf = new Girlfriend4();
}
return gf;
};
}
对多线程不了解社友,看到线程不安全这个词汇,可能会有点懵逼,这里我简单的阐述一下,如何界定一个线程是否安全。
跟上面的方法相比,只是增加了synchronized关键字
package com.cxyxs.designmode.util;
import java.util.concurrent.TimeUnit;
/**
* Description:懒汉式,方法上增加同步
* Author: 程序猿学社
* Date: 2020/3/15 14:00
* Modified By:
*/
public class Demo5 {
public static void main(String[] args) {
new Thread(()->{
Girlfriend5 gf = Girlfriend5.getInstance();
System.out.println(gf);
}).start();
new Thread(()->{
Girlfriend5 gf1 = Girlfriend5.getInstance();
System.out.println(gf1);
}).start();
}
}
class Girlfriend5{
private static Girlfriend5 gf = null;
private Girlfriend5() {
}
public static synchronized Girlfriend5 getInstance(){
if(gf == null){
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
gf = new Girlfriend5();
}
return gf;
};
}
总结:
通过synchronized关键字,解决懒汉式单例线程安全的问题,虽说能保证线程安全,但是,效率太低,在实际项目实战过程中,不建议使用这种方式。
隔壁老王:社长,那有什么办法,可以保证线程安全,效率还行的实现方式吗?
社长:有的,通过双重校验锁方式。
package com.cxyxs.designmode.util;
import java.util.concurrent.TimeUnit;
/**
* Description:懒汉式 双重校验锁 DCL
* Author: 程序猿学社
* Date: 2020/3/15 14:00
* Modified By:
*/
public class Demo6 {
public static void main(String[] args) {
new Thread(()->{
Girlfriend6 gf = Girlfriend6.getInstance();
System.out.println(gf);
}).start();
new Thread(()->{
Girlfriend6 gf1 = Girlfriend6.getInstance();
System.out.println(gf1);
}).start();
}
}
class Girlfriend6{
private static Girlfriend6 gf = null;
private Girlfriend6() {
}
public static Girlfriend6 getInstance(){
if(gf == null){ //步骤1
synchronized (Girlfriend6.class){
if(gf == null){
gf = new Girlfriend6(); //步骤2
}
}
}
return gf;
};
}
这样就会存在问题,假设,他的顺序是1-3-2,线程A刚刚跑到1-3这一步,把gf指向一个地址的时候(表示gf这个对象不为空,因为已经有内存地址),而这时的线程B,刚刚到步骤1,进行对应的判断,发现gf不为空,直接退出程序。
package com.cxyxs.designmode.util;
/**
* Description:
* Author: wude
* Date: 2020/3/18 11:29
* Modified By:
*/
public class Test {
public static void main(String[] args) {
Test test = new Test();
}
}
idea版本中,在类里面右键,保证该类已运行,不然,会提示找不到主类的错。
隔壁老王:社长,既然多线程环境下,会存在指令重排的问题,是不是说通过双重校验锁这种方式实现单例,也不可行。
社长:既然存在指令重排的问题,jdk大佬当然也考虑这个问题,增加volatile关键字
总结:
通过双重检验锁,很好的解决解决线程安全的问题。建议使用。实现了懒加载。
package com.cxyxs.designmode.util;
/**
* Description:
* Author: 程序猿学社
* Date: 2020/3/18 19:59
* Modified By:
*/
public class Demo7 {
public static void main(String[] args) {
Girlfriend7 gf = Girlfriend7.getInstance();
Girlfriend7 gf1 = Girlfriend7.getInstance();
System.out.println(gf == gf1);
}
}
class Girlfriend7{
private Girlfriend7() {
}
public static Girlfriend7 getInstance(){
return Instance.gf;
}
private static class Instance{
private static final Girlfriend7 gf = new Girlfriend7();
}
}
总结:
静态内部类通过classloader 机制来保证单例和线程安全。同时也是懒加载的,只有使用到该静态内部类被调用时,才会被加载。
隔壁老王:社长,社长,听说反射可以破坏单例,具体是怎么一回事?
社长:既然,你提了这个问题,我们就来具体看看,为什么反射可以破坏单例。
实际上,不止反射可以破坏单例,通过序列化的方式也可以破坏单例。
package com.cxyxs.designmode.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
/**
* Description:模拟通过反射破坏单例
* Author: 程序猿学社
* Date: 2020/3/19 9:15
* Modified By:
*/
public class Demo8 {
public static void main(String[] args) throws Exception {
Constructor<Girlfriend8> declaredConstructor = Girlfriend8.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//暴力访问
Girlfriend8 gf = declaredConstructor.newInstance();
Girlfriend8 gf1 = declaredConstructor.newInstance();
System.out.println(gf);
System.out.println(gf1);
}
}
class Girlfriend8{
private Girlfriend8() {
}
public static Girlfriend8 getInstance(){
return Instance.gf;
}
private static class Instance{
private static final Girlfriend8 gf = new Girlfriend8();
}
}
隔壁老王:社长,那怎么解决反射可以暴力破解单例的问题?
社长:在构造方法里面,再判断一下这个对象是否为空。
package com.cxyxs.designmode.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
/**
* Description:模拟通过反射破坏单例
* Author: 程序猿学社
* Date: 2020/3/19 19:15
* Modified By:
*/
public class Demo8 {
public static void main(String[] args) throws Exception {
Constructor<Girlfriend8> declaredConstructor = Girlfriend8.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//暴力访问
Girlfriend8 gf = declaredConstructor.newInstance();
Girlfriend8 gf1 = declaredConstructor.newInstance();
System.out.println(gf);
System.out.println(gf1);
}
}
class Girlfriend8{
private Girlfriend8() {
synchronized (Girlfriend8.class){
if(Instance.gf == null){
try {
throw new Exception("该对象已实例化,不要试图破解!");
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public static Girlfriend8 getInstance(){
return Instance.gf;
}
private static class Instance {
private static final Girlfriend8 gf = new Girlfriend8();
}
}
package com.cxyxs.designmode.util;
import java.lang.reflect.Constructor;
/**
* Description:
* Author: 程序猿学社
* Date: 2020/3/20 19:55
* Modified By:
*/
public enum Demo9 {
GF;
public Demo9 getInstance(){
return GF;
}
}
class Test9{
public static void main(String[] args) throws Exception{
Demo9 instance = Demo9.GF.getInstance();
Demo9 instance1 = Demo9.GF.getInstance();
System.out.println(instance == instance1);
}
}
隔壁老王:社长,为什么枚举可以解决别人暴力破解的问题?
社长: 给你看一段源码,你就知道为什么枚举可以防止别人暴力破解。
还记得我在上一个事例中,给你说过,通过反射可以单例,写了一段代码,我们来看看源码。
最近有不少读者在问我java应该如何学习,在这里,把我整理的学习视频分享出来。
(1).springboot,springcloud视频
(2).架构师视频,设计模式视频,深入jvm内核原理。
(3) java面试视频
可以通过公众号“程序猿学社”,回复关键字"视频",希望能帮到你。
原创不易,不要白嫖,觉得有用的社友,给我点赞,让更多的老铁看到这篇文章。
作者:程序猿学社
原创公众号:『程序猿学社』,暂时专注于java技术栈,分享java各个技术系列专题,以及各个技术点的面试题。
原创不易,转载请注明来源(注明:来源于公众号:程序猿学社, 作者:程序猿学社)。