学习设计模式的第一天,单例模式!!!
1:那么什么是单例模式呢?
单例就是单一、独苗的意思,那什么是独一份呢?你的思维是独一份,除此之外还有什么不能山寨的呢? 我们举个比较难复制的对象:皇帝 中国的历史上很少出现两个皇帝并存的时期,是有,但不多,那我们就认为皇帝是个单例模式,在这 个场景中,有皇帝,有大臣,大臣是天天要上朝参见皇帝的,今天参拜的皇帝应该和昨天、前天的一样(过 渡期的不考虑,别找茬哦),大臣磕完头,抬头一看,嗨,还是昨天那个皇帝,单例模式,绝对的单例模式。
总的来说,不过你见几次,我都是这一个对象,好专一哦!
2:单例模式的简单实现
主要思路就是:
1:定义一个static final变量(在类加载时已经初始化)。
2:将构造函数私有化(不让随便new)。
3:暴露一个静态方法来返回该变量(每次返回都是类初始化时得变量)。
one:饿汉式
解释:
什么是饿汉式,就是不管我用不用,我都提前把它造出来,饿的话 我就直接吃,不用出去买。
/**
* 饿汉式
* 类加载到内存之后,就实例化一个单例,JVM保证线程安全。
* 推荐使用。
* 唯一缺点:就是不管是否会用到,在类装载时就会完成实例化。
*/
public class Demo1 {
private static final Demo1 DEMO_1 = new Demo1();
//构造函数私有化。
private Demo1(){}
//给外界提供一个调用类方法的方法
public static Demo1 getInstance(){
return DEMO_1;
}
public static void main(String[] args) {
//测试,创建两个对象,看看是否是同一个对象。
Demo1 demo1 = Demo1.getInstance();
Demo1 demo2 = Demo1.getInstance();
System.out.println(demo2 == demo1); //答案为true,证明调用的是同一个对象。
}
}
补充:类加载器子系统主要包括加载,连接(验证,准备,解析),初始化等阶段。被static修饰的变量在准备阶段就已经被赋上初始值(int 的话就是0),被static final修饰的变量直接会赋真实值,所以为什么final变量必须得赋值。也所以当类被加载时已经被初始化了。
two:饿汉式另一种表达。
采用静态代码块得方式为static final 变量赋值。
/**
* 和demo1差不多一个意思。只是采用静态代码块的方式为其赋初始值。
*/
public class Demo2 {
private static final Demo2 DEMO_2;
//采用静态代码块的方式给它赋值。
static {
DEMO_2 = new Demo2();
}
public static Demo2 getInstance(){
return DEMO_2;
}
public static void main(String[] args) {
Demo2 demo2 = Demo2.getInstance();
Demo2 demo21 = Demo2.getInstance();
System.out.println(demo21 == demo2); // 为true,证明还是同一个对象。
}
}
three:lazy loading(懒汉式,多线程情况下会出事情)
解释:
(懒汉式就是 我不用就没有,我用的话才创建,为什么,因为我懒!)
主要思路:
1:创建一个static变量。
2:构造函数私有化。
3:对外暴露一个方法,用来获取该变量
进行判断
如果变量为空,则创建一个对象并赋值给static变量。
如果不为空,则证明前辈已经为他创建过该类实例,直接返回。
/**
* lazy loading
* 也称懒汉式
* 虽然达到了按需初始化的目的,但是却带来线程不安全的问题。
*/
public class Demo3 {
private static Demo3 DEMO_3;
//将构造函数私有化。
private Demo3() {};
public static Demo3 getInstance() {
//思路就是如果demo_3 == null,则证明是第一次调用它,创建一个对象为其赋值,
// demo_3 != null,则证明不是第一次调用它,直接返回第一次赋值的结果,
//按道理来说这样是可以保持永远拿到的是第一次创建的对象。
if (DEMO_3 == null) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
DEMO_3 = new Demo3();
}
return DEMO_3;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Demo3.getInstance().hashCode());
//在这里同时创建100个线程,打印出他们的hashcode,发现并不相同,这也就是上面所带来的问题。
//分析问题:其实创建线程之后,他们有可能都走到了( if (DEMO_3 == null) )这一步,多个线程
// 发现都为 DEMO_3 都 == null,所以会创建了多个对象。这样没有达到实现单例的效果。
}).start();
//补充:关于线程并不是说调用了start()方法就会理课执行线程,调用start()方法只会让线程处于就绪状态,
//底层其实是调用start0()方法,他是被native修饰的,也就是说他是C++编写的,存在于本地方法栈,
//至于什么时候开始执行,完全取决于操作系统什么时候为其进行分配资源。
}
}
}
four:懒汉式增强(加锁,避免多线程情况下出事情 √ )
/**
* 针对于懒汉式,也可以解决,就是给他加锁,一个线程一个线程轮流排队去执行,这样不就可以了嘛?
* 但是这样会影响性能。
*/
public class Demo4 {
private static Demo4 DEMO_4;
//构造函数私有化。
private Demo4(){};
//加锁(给静态方法加锁,相当于锁住的是整个类对象)
public static synchronized Demo4 getInstance(){
if(DEMO_4 == null){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
DEMO_4 = new Demo4();
}
return DEMO_4;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() ->{
System.out.println(Demo4.getInstance().hashCode());
}).start();
}
}
}
five:懒汉式增强(另一种加锁,多线程情况下出事情 × )
/**
* 和demo4差不多,这是锁的位置不同
*/
public class Demo5 {
private static Demo5 DEMO_5;
//构造器私有化
private Demo5() {};
public static Demo5 getInstance() {
if (DEMO_5 == null) {
//企图像通过减少同步代码块的方式来提高效率,发现并不可行。
synchronized (Demo5.class) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
DEMO_5 = new Demo5();
}
}
return DEMO_5;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Demo5.getInstance().hashCode());
}).start();
}
}
}
six:懒汉式增强(最终方案,synchronized+双重检测)
/**
* 懒汉式增强,使用双重循环 + synchronized来实现
*/
public class Demo6 {
private static volatile Demo6 DEMO_6;
//构造函数私有化
private Demo6(){};
public static Demo6 getInstance(){
if(DEMO_6 == null){
synchronized (Demo6.class){
//双重循环。
if(DEMO_6 == null){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
DEMO_6 = new Demo6();
}
}
}
return DEMO_6;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Demo6.getInstance().hashCode());
}).start();
}
}
}
seven:静态内部类实现
/**
* 静态内部类的方式。
* JVM来保证单例。
* 加载外部类时不会加载内部类,这样可以实现懒加载。
*/
public class Demo7 {
//构造函数私有化。
private Demo7(){};
//创建静态内部类
private static class Demo7Holder{
private static final Demo7 DEMO_7 = new Demo7();
}
public static Demo7 getInstance(){
return Demo7Holder.DEMO_7;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Demo6.getInstance().hashCode());
}).start();
}
}
}
eight:枚举类(枚举类没有构造方法)
/**
* @author 小泽
* @create 2022-10-19 10:18
* 记得每天敲代码哦
*/
public enum Demo8 {
INSTANCE;
public void m(){}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Demo8.INSTANCE);
}).start();
}
}
}
好了,我所了解到的就这八种单例举例了,可以看出来以上真正实现单例得也就是one,two,four,six,seven,eight。
推荐使用得是one(饿汉式),six(synchronized+双重检测),seven(静态内部类)。
第一天得设计模式学习终于结束了,说实话真的很煎熬,但是还得加油!
生活很苦,但小泽与你相伴,加油!