单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
这个也是23设计模型中最简单理解的一种模型,而本篇就简单聊一下这个模型。
这个模型创建单例在Java中两种方式:
- 饿汉式:其就是在创建类的时候就创建对象。
- 懒汉式:其是在需要用对象的时候才创建。
这个是单例模型中的两种方式,但是具体实现的时候又有划分了,用不同的方式创建的单例模式可以有7种范式。
1:饿汉式—静态常量
先看代码:
public class test {
public static void main(String[] args) {
SingleInstance s1=SingleInstance.getSingleInstance();
SingleInstance s2=SingleInstance.getSingleInstance();
System.out.println(s1==s2);// true
}
}
class SingleInstance{
// 如果有可能带有参数,所以创建一个无参构造函数
public SingleInstance(){}
// 饿汉模式,就是在创建类的时候就创建对象,所以带有关键字static
private final static SingleInstance singleInstance=new SingleInstance();
// 提供一个返回单例的静态方法
public static SingleInstance getSingleInstance(){
return singleInstance;
}
}
- 优点
- 写法简单,在类加载的时候就完成了实例化,避免了线程同步的问题。(不同实例化static的,可以看一下我java基础中文章)
- 缺点:
- 类装载的时候就完成了实例化,如果不用的话机会造成内存的浪费,所以这一方面不如懒汉模式好,毕竟懒汉模式是使用的时候才调用。
2:饿汉式—静态代码块
其实这个和静态常量,区别不大,只不过static关键字放的位置不同,如下:
public class test {
public static void main(String[] args) {
SingleInstance s1 = SingleInstance.getSingleInstance();
SingleInstance s2 = SingleInstance.getSingleInstance();
System.out.println(s1 == s2);// true
}
}
class SingleInstance {
// 如果有可能带有参数,所以创建一个无参构造函数
public SingleInstance() {
}
private static SingleInstance singleInstance;
// 饿汉模式, 创建的对象可以通过代码块创建
static {
SingleInstance singleInstance = new SingleInstance();
}
// 提供一个返回单例的静态方法
public static SingleInstance getSingleInstance() {
return singleInstance;
}
}
其实和饿汉模式的静态常量的优缺点一样的:
- 优点
- 写法简单,在类加载的时候就完成了实例化,避免了线程同步的问题。(不同实例化static的,可以看一下我java基础中文章)
- 缺点:
- 类装载的时候就完成了实例化,如果不用的话机会造成内存的浪费,所以这一方面不如懒汉模式好,毕竟懒汉模式是使用的时候才调用。
3:懒汉式–线程不安全
public class test {
public static void main(String[] args) {
SingleInstance s1 = SingleInstance.getSingleInstance();
SingleInstance s2 = SingleInstance.getSingleInstance();
System.out.println(s1 == s2);// true
}
}
class SingleInstance {
private static SingleInstance singleInstance;
// 如果有可能带有参数,所以创建一个无参构造函数
public SingleInstance() {
}
// 提供一个返回单例的静态方法
public static SingleInstance getSingleInstance() {
// 判断一些是否已创建过,不然每次调用的时候都会创建一次
if(singleInstance==null){
singleInstance=new SingleInstance();
}
return singleInstance;
}
}
看起结果似乎没有问题,但是如果的多线程调用的单例模式就返回的不是同一个对象了:
public class test {
public static void main(String[] args) {
// 多运行几次,因为运行时间很多,有可能返回相同的hashcode
ExecutorService threadPool= Executors.newFixedThreadPool(3);
threadPool.execute(()->{
System.out.println(SingleInstance.getSingleInstance());
});
threadPool.execute(()->{
System.out.println(SingleInstance.getSingleInstance());
});
threadPool.shutdown();
}
}
class SingleInstance {
private static SingleInstance singleInstance;
// 如果有可能带有参数,所以创建一个无参构造函数
public SingleInstance() {
}
// 提供一个返回单例的静态方法
public static SingleInstance getSingleInstance() {
// 判断一些是否已创建过,不然每次调用的时候都会创建一次
if(singleInstance==null){
singleInstance=new SingleInstance();
}
return singleInstance;
}
}
可以看出虽然式单例模式,但是其返回的实例不是同一个。那单例模式还是单例模式吗?
总结:
-
优点:
- 起到了懒汉模式的效果,就是在调用的时候创建实例。
-
缺点:
- 只能在单线程下其效果,但是其在多线程下有可能就是无效的单例模式了。
所以不推荐这种方式,毕竟有同步问题有问题。
4:懒汉式—线程安全
既然上面的同步不安全,那就再方法上加入synchronized进行修饰。
public class test {
public static void main(String[] args) {
ExecutorService threadPool= Executors.newFixedThreadPool(3);
threadPool.execute(()->{
System.out.println(SingleInstance.getSingleInstance());
});
threadPool.execute(()->{
System.out.println(SingleInstance.getSingleInstance());
});
threadPool.shutdown();
}
}
class SingleInstance {
private static SingleInstance singleInstance;
// 如果有可能带有参数,所以创建一个无参构造函数
public SingleInstance() {
}
// 提供一个返回单例的静态方法
public static synchronized SingleInstance getSingleInstance() {
// 判断一些是否已创建过,不然每次调用的时候都会创建一次
if(singleInstance==null){
singleInstance=new SingleInstance();
}
return singleInstance;
}
}
然后多次运行,发现了运行的是同一个对象。
总结:
- 优点:
- 解决了多线程不安全问题,无论多线程还是单线程都可以使用了。
- 缺点:
- 效率很低,多线程 执行get方法的时候,都会被synchronized锁住,所以其效率会低。
不推荐这种方式,毕竟效率会降低。
5:懒汉式—同步代码块
既然上面一种线程安全导致的效率低,那么这样操作呢?
public class test {
public static void main(String[] args) {
// System.out.println(s1 == s2);// true
ExecutorService threadPool= Executors.newFixedThreadPool(3);
threadPool.execute(()->{
System.out.println(SingleInstance.getSingleInstance());
});
threadPool.execute(()->{
System.out.println(SingleInstance.getSingleInstance());
});
threadPool.shutdown();
}
}
class SingleInstance {
private static SingleInstance singleInstance;
// 如果有可能带有参数,所以创建一个无参构造函数
public SingleInstance() {
}
// 提供一个返回单例的静态方法
public static SingleInstance getSingleInstance() {
// 判断一些是否已创建过,不然每次调用的时候都会创建一次
if(singleInstance==null){
synchronized (SingleInstance.class) {
singleInstance = new SingleInstance();
}
}
return singleInstance;
}
}
先看运行的结果:
其实用同步锁住SingleInstance.class,这个想法是没有错的,这样就锁住了同时创建对象。但是 虽然锁住了SingleInstance.class,但是两个线程同时运行到if的时候已经进来了,只不过是在创建对象到时候不能同事通过类创建对象,但是还是会创建两个对象。同时比不带 synchronized (SingleInstance.class) 的运行理论上还要慢,当然都不能保证返回一个对象。优缺点也一样,不再重复说了。
所以这种方式,在开放过程中根本不会用。
6:懒汉式–双重检测同步代码块
public class test {
public static void main(String[] args) {
// System.out.println(s1 == s2);// true
ExecutorService threadPool= Executors.newFixedThreadPool(3);
threadPool.execute(()->{
System.out.println(SingleInstance.getSingleInstance());
});
threadPool.execute(()->{
System.out.println(SingleInstance.getSingleInstance());
});
threadPool.shutdown();
}
}
class SingleInstance {
private static SingleInstance singleInstance;
// 如果有可能带有参数,所以创建一个无参构造函数
public SingleInstance() {
}
// 提供一个返回单例的静态方法
public static SingleInstance getSingleInstance() {
// 第一次判断
if(singleInstance==null){
synchronized (SingleInstance.class) {
// 第二次判断
if(singleInstance==null) {
singleInstance = new SingleInstance();
}
}
}
return singleInstance;
}
}
这种方式,保证了同步线程的安全,同时其效率要比4:懒汉式—线程安全效率要高,毕竟if判断要比完全的锁住get方法开支要少。
在实际开发过程中推荐使用这种方式,来实现懒汉式。
7:懒汉式–静态内部类实现
public class test {
public static void main(String[] args) {
// System.out.println(s1 == s2);// true
ExecutorService threadPool= Executors.newFixedThreadPool(3);
threadPool.execute(()->{
System.out.println(Single.getInstance());
});
threadPool.execute(()->{
System.out.println(Single.getInstance());
});
threadPool.shutdown();
}
}
class Single{
// 利用内部静态类的加载属性
private static class SingleInstance {
private static final Single INSTANCE=new Single();
}
public static Single getInstance(){
return SingleInstance.INSTANCE;
}
}
这种方式采用了类装载的机制来保证初始化实例时只有一个线程。当类被使用时候静态属性,静态方法,以及静态代码块会被实例化。但是静态内部类(SingleInstance)不会被在外部类(Single)不会被立即实例化,只有在调用getInstance方法的时候,才会触发静态内部类,而这个时候也会在第一次的时候实例化其静态属性。而这个是jvm保证了线程的安全,也就是进行初始化的时候别的线程是无法进入的。
其满足了懒汉模式同时其多线程下也是同一个实例,同样也比第5中方式效率要高,所以开发过程中推荐这种方式。
8:懒汉式—枚举实现
public class test {
public static void main(String[] args) {
SingleInstance singleInstance=SingleInstance.INSTANCE;
singleInstance.sayhello();
ExecutorService threadPool= Executors.newFixedThreadPool(3);
threadPool.execute(()->{
System.out.println(SingleInstance.INSTANCE);
});
threadPool.execute(()->{
System.out.println(SingleInstance.INSTANCE);
});
threadPool.shutdown();
}
}
enum SingleInstance{
INSTANCE;
//也可以带有枚举中的方法
public void sayhello(){
System.out.println("testttttttt");
}
}
这个其实用来枚举的特性,因为枚举属性是不可变的,所以其可以避免了多线程同步问题,而且还能防止反序列化重新创建对象,这个也是一些大神推荐的方式,所以这中方式推荐使用。
总结
单例模式保证了系统内存中该类只有一个对象,节省了系统自由,对于一些需要频繁创建销毁的对象,使用单例模式可以提供系统性能。比如:工具类对象,频繁访问数据库或文件对象(比如数据源头,session工厂等)
在选择饿汉式和懒汉式的时候,需要明白一件事情,如果对于某个对象不确定是否会调用那就使用懒汉式,但是如果确定其必然会调用一次,那么直接可以使用饿汉式了。所以两种模式具体使用那种,还是根据自己的业务的需求。