关于单例模式的饿汉式和懒汉式的实现方法

1.singleton(单例模式)
首先单例模式有几个要点:
一是某个类只能有一个实例
得出构造器要私有化
二是它必须自行创建这个实例
含有一个该类的静态变量来保存这个唯一的实例
三是它必须自行向整个系统提供这个实例
对外提供获取该实例对象的方式:
(1)直接暴露
(2)用静态变量的get方法获取
几种常见的单例模式
(1)饿汉式:直接创建对象,不存在线程安全的问题(不管你需不需要,都给你创建出来)
(2) 懒汉式: 延迟创建对象(在你需要的时候才给你创建出来)
下面用代码举例说明上面的饿汉式创建的几种方法

  1. //饿汉式直接创建实例对象不管你需不需要都会创建
    public class Singleton1 {
    //(2自行创建并用静态变量保存
    //(3向外提供这个实例所以把创建的静态变量改成可以向外提供的public类型
    //(4强调是单例所以我们可以用final进行修饰
    public final static Singleton1 INSTANCE = new Singleton1();
    private Singleton1(){
    //(1构造器私有化,
    }
    }
    2.使用枚举的方式创建对象:
    //枚举类型表示该类型的对象是有限的几个
    //我们可以限定为一个,就成了单例
    public enum Singleton2(){
    INSTANCE;
    }
    如果想要访问刚刚的实例对象我们可以通过类名.对象来获取
    import org.w3c.dom.ls.LSOutput;

public class SingleTest {
public static void main(String[] args) {
Singleton1 s = Singleton1.INSTANCE;
System.out.println(s);
}

}
3.静态代码块的形式:
public class Singleton3 {
public static final Singleton3 INSTANCE;
static{
INSTANCE = new Singleton3();
}
private Singleton3(){
//适用于比较复杂的情况,其实和第一种创建方式相同
}
}
懒汉模式创建线程的方法
1.
//构造器任然私有化
// 用静态变量保存唯一的实例
//现在要延时创建这个对象
//提供静态方法来获取这个实例
public class Singleton4 {
private static Singleton4 instance;//所以这里不能用public字段修饰了,因为用户有时候可以直接
//通过类名来获取这个对象,但是这个对象有可能没有实例化,因此需修改程private
private Singleton4(){

}
public static Singleton4 getInstance(){
    //要拿对象的时候就可以调用这个对象了
    if(instance == null){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        instance =  new Singleton4();
    }
    return instance;
}

}
之后我们创建一个测试这个代码的一个类
import java.util.concurrent.*;

public class TestSingleton4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/Singleton4 s = Singleton4.getInstance();
Singleton4 s1 = Singleton4.getInstance();
System.out.println(s==s1);
System.out.println(s);
System.out.println(s1);
/
Callable c = new Callable() {
@Override
public Singleton4 call() throws Exception {
return Singleton4.getInstance();
}
};
ExecutorService es = Executors.newFixedThreadPool(2);
Future f1 = es.submit©;
Future f2 = es.submit©;
Singleton4 s1 = f1.get();
Singleton4 s2 = f2.get();
System.out.println(s1==s2);
System.out.println(s1);
System.out.println(s2);
es.shutdown();
}
}
之后我们可以看看运行结果:
在这里插入图片描述可以看到当判断两个类对象的地址一样不一样的时候,出来的结果是false,两个对象的hashcode也不一样,所以可以得出这样一个结论:懒汉模式在多线程下会创建多个对象的实例化。存在线程安全问题。我们需要修改代码为同步后的代码:

//构造器任然私有化
// 用静态变量保存唯一的实例
//现在要延时创建这个对象
//提供静态方法来获取这个实例
public class Singleton4 {
private static Singleton4 instance;//所以这里不能用public字段修饰了,因为用户有时候可以直接
//通过类名来获取这个对象,但是这个对象有可能没有实例化,因此需修改程private

//构造器任然私有化
// 用静态变量保存唯一的实例
//现在要延时创建这个对象
//提供静态方法来获取这个实例
public class Singleton4 {
private static Singleton4 instance;//所以这里不能用public字段修饰了,因为用户有时候可以直接
//通过类名来获取这个对象,但是这个对象有可能没有实例化,因此需修改程private
private Singleton4(){

}
public static Singleton4 getInstance(){
    //要拿对象的时候就可以调用这个对象了
    if(instance == null){//双重检查效率更高
        synchronized (Singleton4.class){
            if(instance == null){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                instance =  new Singleton4();
            }
        }
    }

    return instance;
}

}

【JVM】为什么静态内部类实现单例模式是线程安全?那么他是如何实现线程安全的?
静态内部类的优点是:外部类加载时并不需要立即加载内部类,
内部类不被加载则不去初始化INSTANCE,故而不占内存。
具体来说当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,
只有当getInstance()方法第一次被调用时,使用INSTANCE的时候,才会导致虚拟机加载SingleTonHoler类。
这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

首先要了解类加载过程中的最后一个阶段:即类的初始化,类的初始化阶本质就是执行类构造器的方法。
方法:这不是由程序员写的程序,而是根据代码由javac编译器生成的。
它是由类里面所有的类变量的赋值动作和静态代码块组成的。JVM内部会保证一个类的
方法在多线程环境下被正确的加锁同步,也就是说如果多个线程同时去进行“类的初始化”,
那么只有一个线程会去执行类的方法,其他的线程都要阻塞等待,直到这个线程执行完方法。
然后执行完方法后,其他线程唤醒,但是不会再进入()方法。也就是说同一个加载器下,
一个类型只会初始化一次。
那么回到这个代码中,这里的静态变量的赋值操作进行编译之后实际上就是一个代码,
当我们执行getInstance方法的时候,会导致SingleTonHolder类的加载,类加载的最后会执行类的初始化,
但是即使在多线程情况下,这个类的初始化的代码也只会被执行一次,所以他只会有一个实例。
import org.w3c.dom.ls.LSOutput;

public class SingleTest {
public static void main(String[] args){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
//Dog() b = new Dog();
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
b.bark();//这里如果直接利用向上转型的方式来创建子类对象由于父类中没有brak方法,就会出错
//解决的办法就是直接用子类的引用指向子类对象
}

}

class Animal{
public void move(){
System.out.println(“动物可以移动”);
}
}

class Dog extends Animal{
public void move(){
System.out.println(“狗可以跑和走”);
}
public void bark(){
System.out.println(“狗可以吠叫”);
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值