2021-07-06 17:28:12 晓掌柜 版权声明:本文为站长原创文章,转载请写明出处
今天我们学习设计模式中一个比较常用的种类:单例模式。同样,我们会从:概念、解决问题、使用
场景、代码示例等角度做一个全方位的剖析。跟着我的节奏,冲!
单例模式是采用一定的方式来保证在整个系统软件中,对某个类仅存在一个实例对象,并且只提供一个
获取其对象实例的方法(静态方法)。
由于这个类只被实例化一次,也就是说不管有多少个类使用到了这个类,也都是只有一个该类的对象。
因此,单例从程序上来说,减少了类实例对象的创建、减小了GC压力、减少了系统资源消耗达到了提升程序
性能的效果。
① 网站计数器:会使用单例,已达到全局同步的效果
② 日志应用程序:一般日志程序都要实时开放,并接受外部调用,使用单例可以更快速高效
③ 数据库连接池:频繁的数据库连接的打开和关闭非常消耗资源,使用单例维护可大大缓解上述问题
④ 多线程的线程池设计:更加方便的对线程池中的线程进行维护和控制处理
⑤ Spring的bean管理器:这个在后续会有专门的文章进行详细介绍
① 构造器私有化(放置被其他类new)
② 类的内部创建对象
③ 对外暴露一个静态的公共方法以获取实例
/**
* @author XA
* date 2021/7/6 16:41
* description: 饿汉式(静态常量)
*/
public class Singletion1 {
/* 构造器私有化 */
private Singletion1(){};
/* 提供静态常量Singletion1,创建实例对象 */
private static Singletion1 instance = new Singletion1();
/* 对外的静态公共方法 */
public static Singletion1 getInstance(){
return instance;
}
}
优点:写法简单,在类加载时就创建了实例对象,避免了线程同步问题。
缺点:在类加载时就完成了实例化,没有达到lazy loading的效果,会导致内存浪费
结论:可以使用,但是会造成内存浪费
① 私有化构造器
② 在静态代码块中创建对象
③ 对外暴露私有公共方法以获取实例
/**
* @author XA
* date 2021/7/6 16:50
* description: 饿汉式-静态代码块
*/
public class Singletion2 {
private Singletion2(){};
private static Singletion2 instance;
static{
instance = new Singletion2();
}
public Singletion2 getInstance(){
return instance;
}
}
和饿汉式的静态常量的实现方式并无太大差异,只不过是将类的实例化放在了静态代码块中。
同样在类状态时初始化,会导致内存浪费!
① 私有构造方法
② 声明静态变量
③ 对外暴露静态公共方法
/**
* @author XA
* date 2021/7/6 16:50
* description: 饿汉式-静态代码块
*/
public class Singletion2 {
private Singletion2(){};
private static Singletion2 instance;
static{
instance = new Singletion2();
}
public Singletion2 getInstance(){
return instance;
}
}
优点:达到了lazy loading的效果。在使用时获取其实例对象,节省内存
缺点:只能单线程使用,多线程时会产生多个实例对象。不建议使用此方法
① 私有构造方法
② 声明静态变量
③ 对外暴露静态公共方法
④ 使用synchronized锁住getInstance方法
/**
* @author XA
* date 2021/7/6 17:03
* description: 懒汉式线程安全-同步方法
*/
public class Singletion4 {
private static Singletion4 instance;
private Singletion4(){};
public static synchronized Singletion4 getInstance(){
if(instance == null){
instance = new Singletion4();
}
return instance;
}
}
优点:解决了线程安全问题
缺点:锁住了整个方法,效率太低了,不建议在开发中使用
① 私有构造方法
② 声明静态变量
③ 对外暴露静态公共方法
④ 使用synchronized锁,进行两次null的检查
/**
* @author XA
* date 2021/7/6 17:09
* description: 懒汉式-线程安全-双重检查
*/
public class Singletion5 {
private static Singletion5 instance;
private Singletion5(){};
private static Singletion5 getInstance(){
if(instance == null){
synchronized (Singletion5.class){
if(instance == null){
instance = new Singletion5();
}
}
}
return instance;
}
}
① 进行了两次instance == null的检查,保证线程安全
② 实例化代码只执行一次
③ 线程安全、延迟加载、效率高
④ 建议在实际开发中使用
/**
* @author XA
* date 2021/7/6 8:56
* description: 单例线程池
*/
public class SingletionThredPool {
/* 核心线程池大小:线程池维护的最小线程数,即使处于空闲状态也不会销毁。默认1 */
private static final int CORE_POOL_SIZE = 50;
/* 线程池最大数量:线程池不会无限制的创建线程,这里是最大容量。默认Integer.MAX_VALUE */
private static final int MAXIMUNPOOL_SIZE = 1000;
/* 空闲线程存活时间:一个线程如果处于空闲时间,并且当前线程数量大于核心线程数时,就会被销毁。默认60s */
private static final int KEEP_ALIVE_TIME = 20;
private ThreadFactory springThreadFactory = new CustomizableThreadFactory("XA-thread-");
private ThreadPoolExecutor executor =
new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUNPOOL_SIZE, KEEP_ALIVE_TIME,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5), springThreadFactory);
private static SingletionThredPool instance = null;
private SingletionThredPool(){};
public void execute(Runnable runnable){
executor.execute(runnable);
}
public void shutdown(){
executor.shutdown();
}
public static SingletionThredPool getInstance(){
if(instance == null){
synchronized (SingletionThredPool.class){
if(instance == null){
instance = new SingletionThredPool();
}
}
}
return instance;
}
}
public class SingletionThredPoolTest implements Runnable{
private SingletionThredPoolTest(){
}
private SingletionThredPoolTest instance = null;
private SingletionThredPoolTest getInstance(){
if(instance == null){
synchronized (SingletionThredPoolTest.class){
if(instance == null){
instance = new SingletionThredPoolTest();
}
}
}
return instance;
}
@Test
public void test() {
long startTime = System.currentTimeMillis();
for (int i=0;i<1000;i++){
SingletionThredPool singletionThredPool = SingletionThredPool.getInstance();
System.out.println("获得的线程实例为:" + singletionThredPool);
SingletionThredPoolTest singletionThredPoolTest = this.getInstance();
System.out.println("当前的测试类为:" + singletionThredPoolTest);
singletionThredPool.execute(singletionThredPoolTest);
}
System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}
@Override
public void run() {
System.out.println("run: " + Thread.currentThread().getName());
}
}
其实在单例的实现方面还有其他的实现方式,这里仅对较为常用的做一个简单介绍。
代码已经上传到git:https://github.com/lfgGuang/design-pattern.git
有一句话这样说,有人一个优秀的船长,你是不是对海域的每一处暗礁都熟知于心,船长说:
我只需要知道哪些海域是安全的就可以了的。
更多单例模式的使用和源码剖析会在后续持续更新,更多精彩请持续关注: