java/ kotlin下的单例模式
单例模式属于创建型模式, 顾名思义,就是说整个系统中只有一个该对象的实例。
为什么要使用单例模式?
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下的单例模式的更多相关文章
- Java 多线程下的单例模式
单例对象(Singleton)是一种常用的设计模式.在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在.正是由于这个特 点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证 ...
- Java与设计模式之单例模式(下) 安全的单例模式
关于单例设计模式,<Java与设计模式之单例模式(上)六种实现方式>介绍了6种不同的单例模式,线程安全,本文介绍该如何保证单例模式最核心的作用——“实现该模式的类有且只有一个实 ...
- Java设计模式之《单例模式》及应用场景
摘要: 原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6510196.html 所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该 ...
- 使用Java+Kotlin双语言的LeetCode刷题之路(三)
BasedLeetCode LeetCode learning records based on Java,Kotlin,Python...Github 地址 序号对应 LeetCode 中题目序号 ...
- Java设计模式之【单例模式】
Java设计模式之[单例模式] 何为单例 在应用的生存周期中,一个类的实例有且仅有一个 当在一些业务中需要规定某个类的实例有且仅有一个时,就可以用单例模式 比如spring容器默认初始化的实例就是单例 ...
- Java与设计模式之单例模式(上)六种实现方式
阎宏博士在<JAVA与模式>中是这样描述单例模式的:作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. ...
- 后端Spring Boot+前端Android交互+MySQL增删查改(Java+Kotlin实现)
1 前言&概述 这篇文章是基于这篇文章的更新,主要是更新了一些技术栈以及开发工具的版本,还有修复了一些Bug. 本文是SpringBoot+Android+MySQL的增删查改的简单实现,用到 ...
- java util 下的concurrent包
------------------------------------------java util 下的concurrent包--------并发包--------------------.jav ...
- 从零开始学 Java - Windows 下安装 Tomcat
谁都想分一杯羹 没有一个人是真正的无私到伟大的,我们试着说着做自己,与人为善,世界和平!殊不知,他们的真实目的当你知道后,你会被恶心到直摇头并下意识地迅速跑开,下辈子都不想见到他.不过,他没错,你也没 ...
随机推荐
- Maven使用--基本入门
maven学习(上)- 基本入门用法 转载自:https://www.cnblogs.com/yjmyzz/p/3495762.html 参考: http://www.cnblogs.com/dave ...
- ScheduledExecutorService中scheduleAtFixedRate方法与scheduleWithFixedDelay方法的区别
ScheduledExecutorService中scheduleAtFixedRate方法与scheduleWithFixedDelay方法的区别 ScheduledThreadPoolExecut ...
- python操作图片
时间:2018-11-30 记录:byzqy 标题:python实现图片操作 地址:https://blog.csdn.net/baidu_34045013/article/details/79187 ...
- 三大操作系统对比使用之·MacOSX
时间:2018-11-13 整理:byzqy 本篇是一篇个人对Mac系统使用习惯和应用推荐的分享,在此记录,以便后续使用查询! 打开终端: command+空格,调出"聚焦搜索(Spotli ...
- vue 之 v-model
v-model虽然很像使用了双向数据绑定的 Angular 的 ng-model,但是 Vue 是单项数据流,v-model 只是语法糖而已: <input v-model="sth& ...
- java agent简介
java agent简介 主要就是两种,一种的方法是premain,一种是agentmain.这两种的区别是: premain是在jvm启动的时候类加载到虚拟机之前执行的 agentmain是可以在j ...
- Java基础之类加载器
Java类加载器是用户程序和JVM虚拟机之间的桥梁,在Java程序中起了至关重要的作用,理解它有利于我们写出更优雅的程序.本文首先介绍了Java虚拟机加载程序的过程,简述了Java类加载器的加载方式( ...
- redis跨实例迁移 & redis上云
1)redis跨实例迁移--源实例db11迁移至目标实例db30 root@fe2e836e4470:/data# redis-cli -a pwd1 -n 11 keys \* |while rea ...
- Django学习day15BBS项目开发2.0
每日测验 """ 今日日考 1.img标签src属性可以指代的值有哪些,各有什么特点 2.pillow模块是干什么用的,主要的方法有哪些 3.简述登陆功能图片验证码相关逻 ...
- PHP多文件上传格式化
文件上传是所有web应用中最常见的功能,而PHP实现这一功能也非常的简单,只需要前端设置表单的 enctype 值为 multipart/form-data 之后,我们就可以通过 $_FILES 获得 ...