Java学习

Java单例模式学习

2020-07-15  本文已影响0人  xiaogp

参考

https://www.cnblogs.com/alsf/p/5517504.html
https://mp.weixin.qq.com/s/nSzXTeSDLNL9CR0Gnt94kw

单例模式概念

单例模式,顾名思义就是在内存只有会创建一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,为了防止频繁地创建对象使得内存飙升单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象.

1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点
主要解决:一个全局使用的类频繁地创建与销毁
何时使用:当您想控制实例数目节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建
关键代码:构造函数是私有的

优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)

缺点:

没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

注意事项:

  1. getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化

懒汉模式和双重校验加锁

懒汉模式创建对象的方法是在程序使用对象前, 先判断该对象是否实例化, 如果已经实例化, 直接返回类对象, 否则执行实例化操作.


懒汉模式.png

代码如下

public class instanceTest {
    private static instanceTest instance;

    private instanceTest() {
    };

    public static instanceTest getInstance() {
        if (instance == null) {
            instance = new instanceTest();
        }
        return instance;
    }
}

懒汉模式的问题
如果两个线程同时判断 instance 为空,那么它们都会去实例化一个instance 对象,这就变成多例了。所以要解决的是线程安全问题。


两个线程同时判断.png

最容易想到的解决方法就是在方法上加锁

public static synchronized instanceTest getInstance() {
            if (instance == null) {
                instance = new instanceTest();
            }
            return instance;
        }

或者对类对象加锁

public static instanceTest getInstance() {
        synchronized (instanceTest.class) {
            if (instance == null) {
                instance = new instanceTest();
            }
        }
        return instance;
    }

这样规避了两个线程同时创建Instance对象的风险,但是每次去获取对象都需要先获取锁,并发性能非常地差,极端情况下,可能会出现卡顿现象
性能优化:
如果没有实例化对象则加锁创建,如果已经实例化了,则不需要加锁,直接获取实例, 所以直接在方法上加锁的方式就被废掉了,因为这种方式无论如何都需要先获取锁
修改代码如下

public static instanceTest getInstance() {
        if (instance == null) {
            synchronized (instanceTest.class) {
                if (instance == null) {
                    instance = new instanceTest();
                }
            }
        }
        return instance;
    }

上面的代码已经完美地解决了并发安全 + 性能低效问题

(1) 如果 Instance不为空,则直接返回对象,不需要获取锁;而如果多个线程发现 Instance 为空,则进入分支.
(2) Instance为空之后, 多个线程尝试争抢同一个锁,只有一个线程争抢成功,第一个获取到锁的线程会再次判断Instance 是否为空,因为Instance 有可能已经被之前的线程实例化
(3) 其它之后获取到锁的线程发现 Instance 已经不为空了,则不会再 new 一个对象,直接返回对象即可
(4) 之后所有进入该方法的线程都不会去获取锁,在第一次判断 Instance 对象时已经不为空了
(5) 因为需要两次判空,且对类对象加锁,该懒汉式写法也被称为:Double Check(双重校验) + Lock(加锁)

完整代码如下

public class instanceTest {
    // 声明成员变量, static能在外部访问到
    // 声明private, 访问需要使用getInstance访问器
    private static instanceTest instance;

    // 构造函数是私有的, 对除了instance之外的参数进行初始化构造
    private instanceTest() {};

    // 声明static可以直接外部访问, 外部通过静态方法访问进行实例化
    // 使用getInstance对instance进行封装, getInstance是构造入口, 同时返回
    // 静态方法内的变量也是静态
    public static instanceTest getInstance() {
        if (instance == null) {  // 检查实例是否已经存在, 否则创建实例instance实例对象
            synchronized (instanceTest.class) {  // 多线程排队创建
                if (instance == null) {
                    instance = new instanceTest();  // 由于是私有化构造器, 外部无法实例化, 所以直接在内部实例化掉
                }
            }
        }
        return instance;
    }

    public static int myFunc(int number) {
        return number + 1;
    }
}

(1) 在外部调用构造器可以创建不同的类实例, 单例设计模式核心就是将类的构造方法私有化, 之后在类的内部产生实例化对象
(2) 在外部直接调用类的静态方法getInstance, 在内部实例化, 并且返回实例化对象给外部
(3) 内部instance通过private私有化不能直接访问, 需要调用getInstance访问器, instance需要static和getInstance静态方法保持一致
(4) 内部构造器对除了instance外的变量进行初始化构造

测试

public class useInstance {
    public static void main(String[] args) {
        int a = instanceTest.getInstance().myFunc(2);
        System.out.println(a);
    }
}

单例模式编写一个Solr查询方法

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;

public class SolrClientTest {
    private static SolrClientTest instance;
    private HttpSolrServer SolrServer;

    private SolrClientTest() {
        SolrServer = new HttpSolrServer("http://localhost:8080/solr/collection3");
    }

    public static SolrClientTest getInstance() {
        if (instance == null) {
            synchronized (SolrClientTest.class) {
                if (instance == null) {
                    instance = new SolrClientTest();
                }
            }
        }
        return instance;
    }

    public static void getEvent(String query) {
        SolrQuery params = new SolrQuery(query);
        try {
            //执行查询,获取响应数据
            QueryResponse response = SolrClientTest.getInstance().SolrServer.query(params);  // 在这里实例化
            //获取数据结果集
            SolrDocumentList list = response.getResults();
            System.out.println("一共获取了" + list.size() + "条结果:");

            for (SolrDocument solrDocument : list) {
                System.out.println("ent:" + solrDocument.getFieldValue("ent"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试调用

public class useInstance {
    public static void main(String[] args) {
        SolrClientTest.getEvent("id:\"1201\"");  // 直接通过静态方法调用, 在getEvent中完成实例化

    }
}

或者

public void getEvent(String query) {
        SolrQuery params = new SolrQuery(query);
        try {
            //执行查询,获取响应数据
            QueryResponse response = this.SolrServer.query(params);
            //获取数据结果集
            SolrDocumentList list = response.getResults();
            System.out.println("一共获取了" + list.size() + "条结果:");

            for (SolrDocument solrDocument : list) {
                System.out.println("ent:" + solrDocument.getFieldValue("ent"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

测试调用

public class useInstance {
    public static void main(String[] args) {
        SolrClientTest.getInstance().getEvent("id:\"1201\"");  // 在这里实例化
    } 
}

单例模式传参

在getInstance中加入参数,如果参数有更新不会调用新的参数,因为已经存在单例
https://blog.csdn.net/liu_yanzhao/article/details/72626605

public class EntAliasMongoUtils {

    private static EntAliasMongoUtils instance;
    private static MongoClient mongoClient;

    private EntAliasMongoUtils(String MongoHost, String MongoPort) {
        mongoClient = new MongoClient(MongoHost, parseInt(MongoPort));
    }

    public static EntAliasMongoUtils getInstance(String MongoHost, String MongoPort) {
        if (instance == null) {
            synchronized (EntAliasMongoUtils.class) {
                if (instance == null) {
                    instance = new EntAliasMongoUtils(MongoHost, MongoPort);
                }
            }
        }
        return instance;
    }
}
上一篇 下一篇

猜你喜欢

热点阅读