单例模式属于创建型模式, 顾名思义,就是说整个系统中只有一个该对象的实例。

为什么要使用单例模式?

1, 对于一些需要频繁创建,销毁的对象, 使用单例模式可以节省系统资源

2, 对于全局持有的对象,单例模式方便控制

实现单例模式的关键?

1, 构造方法私有,阻止对象被外部随意创建

2, 以静态方法返回实例, 因为无法从外部创建,所以要提供静态方法返回实例

3, 确保实例只有1个, 只对类进行一次实例化,以后都取第一次实例化的对象

单例模式常见写法

  • 饿汉式
public class EHan {

//类加载时实例化对象
private static EHan instance = new EHan();

//构造方法私有
public EHan() {

}
//提供静态方法供外部调用返回该类的实例
public static EHan getInstance() {
return instance;
}

public void eat(){
System.out.println("eat...");
}
}
饿汉式单例就是在类初始化时就实例化对象,后面要用的时候直接拿去用就行。 它的优点是线程安全,没有加锁,执行效率高。缺点是没有懒加载,如果这个类自始至终没有真正使用过,那就是浪费了内存
那么饿汉式单例在kotlin中怎么写呢? 我们直接使用studio的一键转换功能,得到如下代码
class EHan {
fun eat() {
println("eat...")
}

companion object {
val instance = EHan()
}
}
其实在kotlin中还可以写得更简单:
object EHan {
fun eat() {
println("eat...")
}
}
一个object关键字就申明了饿汉式单例, 为了证明, 我们可以通过studio查看上述代码的字节码
tools--> kotlin--> show kotlin Bytecode. --> DeCompile 得到结果如下

这是因为在kotlin中,类没有静态方法. object关键字在kotlin中有两种使用场景, 对象表达式和对象声明。对象声明就是在kotlin中声明单例的方式

上面的companion object也是一样的道理, 不同的是 伴生对象在整个类中只能有1个

  • 懒汉式

懒汉式分为线程安全的和线程不安全的, 我们先来看不安全的

public class LanHan {

private static LanHan instance;

//构造方法私有
private LanHan(){

}

//提供静态方法返回实例
public static LanHan getInstance(){
//如果为空就去实例化, 否则返回instance
if (instance == null) {
instance = new LanHan();
}
return instance;
}
}
它对应的在kotlin中的写法如下:
class LanHan private constructor() {
companion object {
private var instance: LanHan? = null
get() {
if (field == null) {
field = LanHan()
}
return field
}

fun get(): LanHan {
return instance!!
}
}
}

这种写法,如果某个线程执行到if (instance == null) 还没来得及往下执行时,另一个线程也执行到了这里,那么就会创建两个实例, 所以是线程不安全的。
那么如何让它线程安全呢?答案是synchronized关键字
public class LanHan {

private static LanHan instance;

//构造方法私有
private LanHan(){

}

//提供静态方法返回实例
public static LanHan getInstance(){
//加锁, 防止多个线程同时执行实例化
synchronized (LanHan.class){
//如果为空就去实例化, 否则返回instance
if (instance == null) {
instance = new LanHan();
}
}
return instance;
}
}
在kotlin中的写法:
class LanHan private constructor() {
companion object {
private var instance: LanHan? = null
get() {
if (field == null) {
field = LanHan()
}
return field
}

@Synchronized
fun get(): LanHan {
return instance!!
}
}
}
在kotlin中,@Synchronized注解表明给该方法加同步锁
加锁之后, 实现了线程安全, 多个线程执行到这里时要排队等待, 这又严重影响了性能, 怎么解决呢?

方案一: 双重锁(DCL)
所谓双重锁就是判断两次if(instance == null), 只有等于null时才上锁
public static LanHan getInstance(){
//只有对象未创建时才上锁
if (instance == null){
//加锁, 防止多个线程同时执行实例化
synchronized (LanHan.class){
//如果为空就去实例化, 否则返回instance
if (instance == null) {
instance = new LanHan();
}
}
}
return instance;
}

DCL是完美的解决了单例模式中性能和资源浪费的问题,但是DCL在并发情下也会存在一个问题,因为Jvm指令是乱序的;

情况如下:

线程1调用getInstance 获取对象实例,因为对象还是空未进行初始化,此时线程1会执行new Singleton()进行对象实例化,而当线程1的进行new Singleton()的时候JVM会生成三个指令。

指令1:分配对象内存。

指令2:调用构造器,初始化对象属性。

指令3:构建对象引用指向内存。

因为编译器会自作聪明的对指令进行优化, 指令优化后顺序会变成这样:

1、执行指令1:分配对象内存,

2、执行指令3:构建对象引用指向内存。

3、然后正好这个时候CPU 切到了线程2工作,而线程2此时也调用getInstance获取对象,那么线程2将执行下面这个代码 if (singleton == null),此时线程2发现对象不为空(因为线程1已经创建对象引用并分配对象内存了),那么线程2会得到一个没有初始化属性的对象(因为线程1还没有执行指令2)。

所以在这种情况下,双检测锁定的方式会出现DCL失效的问题。

那么双重锁在kotlin代码里怎么写呢, 我们先直接一键转换看一下

object LanHan {
var instance: LanHan? = null
get() {
if (field == null) {
synchronized(LanHan::class.java) {
if (field == null) {
field = LanHan
}
}
}
return field
}
private set
}
看起来是不是很messy, 有没有更优雅的写法呢, 百度了一下果然有
class LanHan private constructor() {
companion object {
val instance: LanHan by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
LanHan()
}
}
}
这个看起来就简洁多了, 这里有个知识点就是lazy
Lazy是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托:第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

(摘自 什么是单例模式_东非大裂谷的博客-CSDN博客_什么是单例模式

方案二: 静态内部类

public class LanHan {

//构造方法私有
private LanHan(){}

//静态内部类
private static class LanHanHolder{
//内部类第一次访问时创建对象实例
private static LanHan instance = new LanHan();
}

public static LanHan getInstance(){
return LanHanHolder.instance;
}
}
因为当外部类被访问时并不会加载内部类, 所以只有在访问LanHanHolder这个内部类时才会去实例化对象,这就实现了懒加载的效果.但是静态内部类的方式只适用于静态域
可以看到单例模式的各种写法里都用到了static关键字, 这是因为static修饰的语句/代码块在整个类加载过程中只执行一次。

那么静态内部类在kotlin中怎么写呢?
class LanHan private constructor() {

private object LanHanHolder {
val holder = LanHan()
}

companion object{
val instance = LanHanHolder.holder
}
}
  • 枚举
public enum MeiJu {
INSTANCE;
public void run(){
}
}
这是一种非常简单的单例实现, 避免了线程同步问题,支持自动序列化机制,且绝对不会多次实例化
枚举的特性:
1, 它不能有public的构造函数, 这样就保证了其他代码没有办法新建一个enum实例。
2,所有枚举值都是public, static, final 的
3, Enum默认实现了jave.lang.comparable接口
4, Enum覆载了toString方法
5, Enum提供了valueOf方法, 用于从string中读取枚举类型
6, Enum提供了values方法, 用来遍历所有的枚举值
枚举在kotlin中的写法和java大同小异, 不再赘述
enum class MeiJu {
INSTANCE;
fun run() {}
}

(kotlin部分参考 Kotlin下的5种单例模式,看了都说好!_程序员小乐-CSDN博客 感谢博主)


java/ kotlin下的单例模式的更多相关文章

  1. Java 多线程下的单例模式

    单例对象(Singleton)是一种常用的设计模式.在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在.正是由于这个特 点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证 ...

  2. Java与设计模式之单例模式(下) 安全的单例模式

          关于单例设计模式,<Java与设计模式之单例模式(上)六种实现方式>介绍了6种不同的单例模式,线程安全,本文介绍该如何保证单例模式最核心的作用——“实现该模式的类有且只有一个实 ...

  3. Java设计模式之《单例模式》及应用场景

    摘要: 原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6510196.html 所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该 ...

  4. 使用Java+Kotlin双语言的LeetCode刷题之路(三)

    BasedLeetCode LeetCode learning records based on Java,Kotlin,Python...Github 地址 序号对应 LeetCode 中题目序号 ...

  5. Java设计模式之【单例模式】

    Java设计模式之[单例模式] 何为单例 在应用的生存周期中,一个类的实例有且仅有一个 当在一些业务中需要规定某个类的实例有且仅有一个时,就可以用单例模式 比如spring容器默认初始化的实例就是单例 ...

  6. Java与设计模式之单例模式(上)六种实现方式

           阎宏博士在<JAVA与模式>中是这样描述单例模式的:作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类.      ...

  7. 后端Spring Boot+前端Android交互+MySQL增删查改(Java+Kotlin实现)

    1 前言&概述 这篇文章是基于这篇文章的更新,主要是更新了一些技术栈以及开发工具的版本,还有修复了一些Bug. 本文是SpringBoot+Android+MySQL的增删查改的简单实现,用到 ...

  8. java util 下的concurrent包

    ------------------------------------------java util 下的concurrent包--------并发包--------------------.jav ...

  9. 从零开始学 Java - Windows 下安装 Tomcat

    谁都想分一杯羹 没有一个人是真正的无私到伟大的,我们试着说着做自己,与人为善,世界和平!殊不知,他们的真实目的当你知道后,你会被恶心到直摇头并下意识地迅速跑开,下辈子都不想见到他.不过,他没错,你也没 ...

随机推荐

  1. shiro(二)

    public class AuthorizerTest { @Test public void testIsPermitted() { login("classpath:shiro-auth ...

  2. Java学习之 多态 Polymorphism

    转自:http://www.cnblogs.com/mengdd/archive/2012/12/25/2832288.html 多态的概念 多态==晚绑定. 不要把函数重载理解为多态. 因为多态是一 ...

  3. tree命令出现乱码

    alias tree='tree --charset ASCII'就可以了

  4. Linux常用命令(一)之文件处理命令

    分时的多用户.多任务的操作系统 多数的网络协议的支持(unix和tcp/ip协议是同时发展起来的),方便的远程管理(可以通过图形.命令行) 强大的内存管理和文件管理系统 大量的可用软件和免费软件(游戏 ...

  5. JVM加载class文件的一些理解

    Java是一种动态解释型语言,类(class)只有被加载到JVM中后才能运行.每当一个Java程序运行时,都会有一个对应的JVM实例,只有当程序运行结束后,这个JVM才会退出.JVM实例通过调用类的m ...

  6. vue之 分页封装

    npm 下载 npm i element-ui -S components 创建 Page 文件夹 创建 Page.vue 文件 vue 文件 <template>   <div c ...

  7. 性能测试工具JMeter 基础(三)—— 创建测试计划

    如何创建一个完整的测试计划? 安装好JMeter后,通过根目录下的bin目录中的jmeter.bat启动JMeter 添加线程组(Thread Group) 在 Test Plan 鼠标右键 Add- ...

  8. shell脚本之case语句

    case ... esac 为多选择语句,与其他语言中的 switch ... case 语句类似,是一种多分枝选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行 ...

  9. 【Azure 应用服务】Python flask 应用部署在Aure App Service中作为一个子项目时,解决遇见的404 Not Found问题

    问题描述 在成功的部署Python flask应用到App Service (Windows)后,如果需要把当前项目(如:hiflask)作为一个子项目(子站点),把web.config文件从wwwr ...

  10. Linux常用命令 - nl命令详解

    21篇测试必备的Linux常用命令,每天敲一篇,每次敲三遍,每月一循环,全都可记住!! https://www.cnblogs.com/poloyy/category/1672457.html 显示行 ...