资讯专栏INFORMATION COLUMN

设计模式 单例模式

Sourcelink / 3375人阅读

摘要:单例模式确保一个类只有一个实例,而且自动实例化并向整个系统提供这个实例。将构造函数设置为私有的,防止外界出该类的实例,从而失去了单例的意义。这种实现的单例模式是最简单的,同时多个线程操作该单例时也不会有问题。这就违反了单例模式。

单例模式

确保一个类只有一个实例,而且自动实例化并向整个系统提供这个实例。

实现 饿汉式

很简单。

将构造函数设置为私有的,防止外界new出该类的实例,从而失去了单例的意义。

设置类的私有静态变量,同时新建单例对象。

添加共有静态方法获取该单例。

该种方法的缺点是在类加载时就进行实例化,但是相较于其简单易用来说,这点缺点个人认为影响不大。

package com.mengyunzhi;

/**
 * @author zhangxishuo on 2018/6/18
 */
public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return instance;
    }
}

这种实现的单例模式是最简单的,同时多个线程操作该单例时也不会有问题。

package com.mengyunzhi;

public class Main {

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            Singleton singleton1 = Singleton.getInstance();
            System.out.println("Singleton1:" + singleton1);
        });
        Thread thread2 = new Thread(() -> {
            Singleton singleton2 = Singleton.getInstance();
            System.out.println("Singleton2:" + singleton2);
        });
        thread1.start();
        thread2.start();
    }
}

注:打印时调用toString方法,因为没有重写toString,调用Object类中的toString,所以打印该对象的类名加哈希值。

我们看到控制台中打印的两个对象地址都是89ae60d,表示同一块内存,即表示多线程时该实现方法仍能实现单例。

线程竞争

我们调用的顺序明明是thread1start,然后thread2start,但是为什么控制台打印的顺序却是单例2和单例1呢?

这两个线程会竞争处理器的资源,这里打印的顺序是单例2、单例1,说明处理器处执行线程时,先执行完thread2线程,后执行完thread1线程。这两个线程可能是同时执行,也可能是来回切换执行,这取决于处理器的核心与线程。

代码讲解

函数式接口

Thread类的构造函数接收的是一个Runnable接口类型的参数,所以之前创建线程的代码长这样。

Thread myThread = new Thread(new Runnable() {
    @Override
    public void run() {

    }
});

看下面的代码,因为Runnable接口只有一个抽象的run方法需要去实现,所以就不需要去@Override声明我要实现run方法,直接传一个函数体不就可以吗?这就是函数式接口。

package java.lang;

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

lamda表达式

相信很多人都听说过lamda表达式,但是总是觉得这个很高大上,其实我们接触过lamda表达式,只是没有注意到。

self.init = function() {

};

JavaScript的世界里,我们可以将一个函数传来传去。

self.init = () => {

};

然后人们发现,写function太麻烦了,他们发明了箭头函数。用这种写法代替一个函数。

Java为什么不可以?

如果刚刚的代码这么写,那你应该瞬间就明白了。

Runnable runnable = () -> {
    Singleton singleton1 = Singleton.getInstance();
    System.out.println("Singleton1:" + singleton1);
};
Thread thread1 = new Thread(runnable);
懒汉式

这是懒汉式的写法,当需要这个实例的时候,再去新建实例。

package com.mengyunzhi;

/**
 * @author zhangxishuo on 2018/6/18
 */
public class Singleton {

    private static Singleton instance;

    private Singleton() {

    }

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

但是这是这种写法是线程不安全的,我们再运行一下主函数中多个线程同时访问单例的方法。

上次向晨澍请教:StringBuffer线程安全,StringBuilder线程不安全。既然有的类线程安全,有的类线程不安全?那为什么不都用线程安全的呢?

答案就是:为了实现线程安全,系统需要额外的开销。所以有些不需要多线程的,使用线程不全的类,通常会提高速度。

假设我们的处理器支持多个线程并行处理,当多个线程同时访问时,thread1获取实例,然后判断if (instance == null),创建实例;另一个线程同时执行,instance依然是空,然后thread2调用getInstance时又创建了一个实例。这就违反了单例模式。

synchronized

解决该问题的方案就是用synchronized修饰该代码块。

音标:["sɪŋkrənaɪzd]

只允许一个线程访问synchronized修饰的代码块,其他线程会被阻塞,等待该线程执行完再执行。

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

应该是线程2先执行完的,所以我们猜测就是:线程2竞争到处理器资源,然后去访问getInstance()方法,因为笔者属于多核多线程处理器,支持线程并行执行,当线程2访问getInstance()代码块时,因为有synchronized修饰,所以线程1会被阻塞,等待线程2执行完再才能访问该代码块。

线程2执行完创建实例,线程1可以访问该代码块,发现instance不为空,直接返回。

使用场景

频繁new然后销毁的对象,降低了内存开支。

当一个对象的产生需要较多资源时,如读取配置,可以将其设置为单例,在应用启动时产生一个单例对象常驻内存。

单例是同一个对象,可以用该单例设置项目配置,用于几个模块之间共享。

扩展:多线程学习 为什么要使用多线程?

摩尔定律

每18个月,芯片的性能将提高一倍。

单核心

十几年前,那时还是单核的时代,各大厂商做出主频越来越高的处理器。

但是主频越高,意味着芯片中需要的晶体管越多,功耗越大,散热越多,当一定程度热量就会烧坏芯片。

为什么CPU的频率止步于4G?我们触到频率天花板了吗?

2004年秋,IntelCEO公开对取消4GHz芯片的计划道歉。

这是Intel酷睿i7 8700K的参数,主频仅有3.70GHz,十几年过去了,我们依然停留在4GHz

多核心

但是,为了满足不断增长的用户需求,虽然无法提升单个核心的时钟频率,但是厂商利用多核心实现了芯片的性能提升。

这是Intel官网对i7 8700K的描述,6核心12线程。也就是说这个处理器有6个物理核心,因为超线程技术,可以模拟出12个逻辑核心,即可以同时处理12个线程任务。

超线程就是利用处理器剩余的资源模拟出一个新的核心,用于提高处理器的利用率。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71245.html

相关文章

  • Android中的设计模式单例模式

    摘要:总结单例是运用频率很高的模式,因为客户端没有高并发的情况,选择哪种方式并不会有太大的影响,出于效率考虑,推荐使用和静态内部类实现单例模式。 单例模式介绍 单例模式是应用最广的模式之一,也可能是很多人唯一会使用的设计模式。在应用单例模式时,单例对象的类必须保证只用一个实例存在。许多时候整个系统只需要一个全局对象,这样有利于我么能协调整个系统整体的行为。 单例模式的使用场景 确保某个类有且...

    yzd 评论0 收藏0
  • JavaScript设计模式----单例模式

    摘要:不符合设计模式中的单一职责的概念。引入代理实现单例模式引入代理实现单例模式的特点我们负责管理单例的逻辑移到了代理类中。的单例模式对比在以上的代码中实现的单例模式都混入了传统面向对象语言的特点。 声明:这个系列为阅读《JavaScript设计模式与开发实践》 ----曾探@著一书的读书笔记 1.单例模式的特点和定义 保证一个类仅有一个实例,并且提供一个访问它的全局访问点。 2.传统面向对...

    selfimpr 评论0 收藏0
  • JavaScript设计模式-第一部分:单例模式、组合模式和外观模式

    摘要:但是,这并不是采用单例的唯一原因。使用命名空间单例模式也被称为模块设计模式。函数内部声明了一些局部函数和或变量。紧随函数声明放置即可立即执行外部函数,并将所得的对象文字费赔给变量。 JavaScript设计模式-第一部分:单例模式、组合模式和外观模式 设计模式是一些可靠的编程方式,有助于保证代码更加易于维护、扩展及分离,所有设计模式在创建大型JavaScript应用程序时均不可或缺 单...

    betacat 评论0 收藏0
  • Java设计模式-单例模式(Singleton Pattern)

    摘要:如果需要防范这种攻击,请修改构造函数,使其在被要求创建第二个实例时抛出异常。单例模式与单一职责原则有冲突。源码地址参考文献设计模式之禅 定义 单例模式是一个比较简单的模式,其定义如下: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 或者 Ensure a class has only one instance, and provide a global point of ac...

    k00baa 评论0 收藏0
  • Java 设计模式单例模式

    摘要:在设计模式一书中,将单例模式称作单件模式。通过关键字,来保证不会同时有两个线程进入该方法的实例对象改善多线程问题为了符合大多数程序,很明显地,我们需要确保单例模式能在多线程的情况下正常工作。 在《Head First 设计模式》一书中,将单例模式称作单件模式。这里为了适应大环境,把它称之为大家更熟悉的单例模式。 一、了解单例模式 1.1 什么是单例模式 单例模式确保一个类只有一个实例,...

    everfight 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<