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
谁都想分一杯羹 没有一个人是真正的无私到伟大的,我们试着说着做自己,与人为善,世界和平!殊不知,他们的真实目的当你知道后,你会被恶心到直摇头并下意识地迅速跑开,下辈子都不想见到他.不过,他没错,你也没 ...
随机推荐
- YOLO V3 原理
基本思想V1: 将输入图像分成S*S个格子,每隔格子负责预测中心在此格子中的物体. 每个格子预测B个bounding box及其置信度(confidence score),以及C个类别概率. bbox ...
- chrome插件开发学习(一)
两个不错的网址: 360chrome插件开发文档:http://open.chrome.360.cn/extension_dev/manifest.html 图灵 chrome插件开发于应用 电子书: ...
- vue-cli坑比系列
Error loading saved preferences: ~/.vuerc may be corrupted or have syntax errors. Please fix/delete ...
- Linux常用命令 - 五种创建文件命令详解
21篇测试必备的Linux常用命令,每天敲一篇,每次敲三遍,每月一循环,全都可记住!! https://www.cnblogs.com/poloyy/category/1672457.html 创建文 ...
- Vue CSS模拟菜单点击变色
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8&quo ...
- Linux的基础指令(二)
目录: 一.列表显示目录内容-ls 二.设置别名-alias 三.统计目录及文件空间占用情况-du 四.创建新目录 -mkdir 五.创建空文件-to ...
- (2)java Spring Cloud+Spring boot+mybatis企业快速开发架构之SpringCloud-Spring Cloud是什么?Spring Cloud版本介绍
Spring Cloud 是一系列框架的有序集合.它利用 Spring Boot 的开发便利性,巧妙地简化了分布式系统基础设施的开发,如服务注册.服务发现.配置中心.消息总线.负载均衡.断路器.数 ...
- Vue个人博客关于标题自动打字机效果Typewriter
最近在写个人Blog 中间看过很多个人博客的开发 一大部分用的是Hexo框架或者vuePress框架 导入各种主题样式插件等等 但是看多了就会发现 很多博主的个人博客基本都很相似 并没有什么新东西呈现 ...
- pip国内源设置
在目录 C:\Users\Administrator下新建pip目录 C:\Users\Administrator\pip 添加 pip.ini 文件 pip.ini内容设置为 [global] in ...
- C++打字小游戏
从@小蔡编程 那里获得的灵感,原文地址:https://www.cnblogs.com/xiaocaibiancheng/p/15158997.html 那篇文章是说python写打字游戏的,本文就用 ...