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

为什么要使用单例模式?

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. 使用GZIP压缩网页内容(一)

    在JDK中提供了GZIP压缩,来压缩网页的内容,降低网络传输时候的字节数,到达浏览器端的时候,再解压,GZIP压缩之后传输耗费的流量大大降低,但是同时也不会降低用户体验. package day04; ...

  2. 笔记本Linux系统,修改合盖不待机

    最近买了一个新笔记本,所以就把老的笔记本当作服务器使用了.但是一盒笔记本的盖子就会待机,真的是麻烦.操作如下可以解决问题: 1.编辑 logind.conf 文件,命令如下 vi /etc/syste ...

  3. roscore启动不完全问题

    运行roscore,得到如下日志,且一直卡着无法继续执行 ... logging to /home/xbit/.ros/log/79f2952c-589c-11ea-8213-d0abd5e7d222 ...

  4. win+R 中的命令

    cmd------CMD命令提示符 MSConfig------系统配置实用程序 regedit------注册表编辑器 notepad------打开记事本 calc------启动计算器 msts ...

  5. shell中的引号

    单引号: 所见即所得 原封不动输出 双引号: 与单引号类似 特殊符号进行解析 ( $ $() `` ! ) 无引号: 与双引号类似 支持通配符( {} * ) 反引号: 优先执行 优先执行里面的命令, ...

  6. Git - Mac 电脑使用 brew 更新 Git

    安装 Homebrew Homebrew 是一个软件包管理器.它的作用就是将软件包安装到自己的目录中,然后将其文件符号链接到 /usr/local.更多信息,请自行进入官网查看 https://bre ...

  7. C# Dapper基本三层架构使用 (四、WinForm UI层)

    UI层主要功能是显示数据和接受传输用户的数据,可以在为网站的系统运行提供交互式操作界面,表示层的应用方式比较常见,例如Windows窗体和Web页面. 在项目中增加WinForm应用程序,结构如下 添 ...

  8. outerHTML和outerText的赋值是异步的

    用JavaScript操作DOM时,经常有生成复杂HTML结构的需求.此时,通常不是用标准DOM接口(如createElement().setAttribute().append()等)来语句式地生成 ...

  9. 在 Docker 的 CentOS7 镜像 中安装 mysql

    在 Docker 的 CentOS7 镜像 中安装 mysql 本来以为是个很简单的过程居然折腾了这么久,之前部署云服务器时也没有好好地记录,因此记录下. 特别提醒:本文的操作环境是在 Docker ...

  10. 【C++周报】第一期2021-8-1

    [C++周报]第一期 2021-8-1 这一期我们来看这道题目:https://vijos.org/p/1058 这道题是一道非常好的模拟题.题目如下: 描述 我们用文本处理器来处理一个特殊的文本文件 ...