单例模式,是特别常见的一种设计模式,因此我们有必要对它的概念和几种常见的写法非常了解,而且这也是面试中常问的知识点。

所谓单例模式,就是所有的请求都用一个对象来处理,如我们常用的Spring默认就是单例的,而多例模式是每一次请求都创建一个新的对象来处理,如structs2中的action。

使用单例模式,可以确保一个类只有一个实例,并且易于外部访问,还可以节省系统资源。如果在系统中,希望某个类的对象只存在一个,就可以使用单例模式。

那怎么确保一个类只有一个实例呢?

我们知道,通常我们会通过new关键字来创建一个新的对象。这个时候类的构造函数是public公有的,你可以随意创建多个类的实例。所以,首先我们需要把构造函数改为private私有的,这样就不能随意new对象了,也就控制了多个实例的随意创建。

然后,定义一个私有的静态属性,来代表类的实例,它只能类内部访问,不允许外部直接访问。

最后,通过一个静态的公有方法,把这个私有静态属性返回出去,这就为系统创建了一个全局唯一的访问点。

以上,就是单例模式的三个要素。总结为:私有构造方法

指向自己实例的私有静态变量

对外的静态公共访问方法

单例模式分为饿汉式和懒汉式。它们的主要区别就是,实例化对象的时机不同。饿汉式,是在类加载时就会实例化一个对象。懒汉式,则是在真正使用的时候才会实例化对象。

饿汉式单例代码实现:

public class Singleton {

// 饿汉式单例,直接创建一个私有的静态实例

private static Singleton singleton = new Singleton();

//私有构造方法

private Singleton(){

}

//提供一个对外的静态公有方法

public static Singleton getInstance(){

return singleton;

}

}

懒汉式单例代码实现

public class Singleton {

// 懒汉式单例,类加载时先不创建实例

private static Singleton singleton = null;

//私有构造方法

private Singleton(){

}

//真正使用时才创建类的实例

public static Singleton getInstance(){

if(singleton == null){

singleton = new Singleton();

}

return singleton;

}

}

稍有经验的程序员就发现了,以上懒汉式单例的实现方式,在单线程下是没有问题的。但是,如果在多线程中使用,就会发现它们返回的实例有可能不是同一个。我们可以通过代码来验证一下。创建十个线程,分别启动,线程内去获得类的实例,把实例的 hashcode 打印出来,只要相同则认为是同一个实例;若不同,则说明创建了多个实例。

public class TestSingleton {

public static void main(String[] args) {

for (int i = 0; i < 10 ; i++) {

new MyThread().start();

}

}

}

class MyThread extends Thread {

@Override

public void run() {

Singleton singleton = Singleton.getInstance();

System.out.println(singleton.hashCode());

}

}

/**

运行多次,就会发现,hashcode会出现不同值

668770925

668770925

649030577

668770925

668770925

668770925

668770925

668770925

668770925

668770925

*/

所以,以上懒汉式的实现方式是线程不安全的。那饿汉式呢?你可以手动测试一下,会发现不管运行多少次,返回的hashcode都是相同的。因此,认为饿汉式单例是线程安全的。

那为什么饿汉式就是线程安全的呢?这是因为,饿汉式单例在类加载时,就创建了类的实例,也就是说在线程去访问单例对象之前就已经创建好实例了。而一个类在整个生命周期中只会被加载一次。因此,也就可以保证实例只有一个。所以说,饿汉式单例天生就是线程安全的。(可以了解一下类加载机制)

既然懒汉式单例不是线程安全的,那么我们就需要去改造一下,让它在多线程环境下也能正常工作。以下介绍几种常见的写法:

1) 使用synchronized方法

实现非常简单,只需要在方法上加一个synchronized关键字即可

public class Singleton {

private static Singleton singleton = null;

private Singleton(){

}

//使用synchronized修饰方法,即可保证线程安全

public static synchronized Singleton getInstance(){

if(singleton == null){

singleton = new Singleton();

}

return singleton;

}

}

这种方式,虽然可以保证线程安全,但是同步方法的作用域太大,锁的粒度比较粗,因此,执行效率就比较低。

2) synchronized 同步块

既然,同步整个方法的作用域大,那我缩小范围,在方法里边,只同步创建实例的那一小部分代码块不就可以了吗(因为方法较简单,所以锁代码块和锁方法没什么明显区别)。

public class Singleton {

private static Singleton singleton = null;

private Singleton(){

}

public static Singleton getInstance(){

//synchronized只修饰方法内部的部分代码块

synchronized (Singleton.class){

if(singleton == null){

singleton = new Singleton();

}

}

return singleton;

}

}

这种方法,本质上和第一种没什么区别,因此,效率提升不大,可以忽略不计。

3) 双重检测(double check)

可以看到,以上的第二种方法只要调用getInstance方法,就会走到同步代码块里。因此,会对效率产生影响。其实,我们完全可以先判断实例是否已经存在。若已经存在,则说明已经创建好实例了,也就不需要走同步代码块了;若不存在即为空,才进入同步代码块,这样可以提高执行效率。因此,就有以下双重检测了:

public class Singleton {

//注意,此变量需要用volatile修饰以防止指令重排序

private static volatile Singleton singleton = null;

private Singleton(){

}

public static Singleton getInstance(){

//进入方法内,先判断实例是否为空,以确定是否需要进入同步代码块

if(singleton == null){

synchronized (Singleton.class){

//进入同步代码块时也需要判断实例是否为空

if(singleton == null){

singleton = new Singleton();

}

}

}

return singleton;

}

}

需要注意的一点是,此方式中,静态实例变量需要用volatile修饰。因为,new Singleton() 是一个非原子性操作,其流程为:

a.给 singleton 实例分配内存空间

b.调用Singleton类的构造函数创建实例

c.将 singleton 实例指向分配的内存空间,这时认为singleton实例不为空

正常顺序为 a->b->c,但是,jvm为了优化编译程序,有时候会进行指令重排序。就会出现执行顺序为 a->c->b。这在多线程中就会表现为,线程1执行了new对象操作,然后发生了指令重排序,会导致singleton实例已经指向了分配的内存空间(c),但是实际上,实例还没创建完成呢(b)。

这个时候,线程2就会认为实例不为空,判断 if(singleton == null)为false,于是不走同步代码块,直接返回singleton实例(此时拿到的是未实例化的对象),因此,就会导致线程2的对象不可用而使用时报错。

4)使用静态内部类

思考一下,由于类加载是按需加载,并且只加载一次,所以能保证线程安全,这也是为什么说饿汉式单例是天生线程安全的。同样的道理,我们是不是也可以通过定义一个静态内部类来保证类属性只被加载一次呢。

public class Singleton {

private Singleton(){

}

//静态内部类

private static class Holder {

private static Singleton singleton = new Singleton();

}

public static Singleton getInstance(){

//调用内部类的属性,获取单例对象

return Holder.singleton;

}

}

而且,JVM在加载外部类的时候,不会加载静态内部类,只有在内部类的方法或属性(此处即指singleton实例)被调用时才会加载,因此不会造成空间的浪费。

5)使用枚举类

因为枚举类是线程安全的,并且只会加载一次,所以利用这个特性,可以通过枚举类来实现单例。

public class Singleton {

private Singleton(){

}

//定义一个枚举类

private enum SingletonEnum {

//创建一个枚举实例

INSTANCE;

private Singleton singleton;

//在枚举类的构造方法内实例化单例类

SingletonEnum(){

singleton = new Singleton();

}

private Singleton getInstance(){

return singleton;

}

}

public static Singleton getInstance(){

//获取singleton实例

return SingletonEnum.INSTANCE.getInstance();

}

}

java单例方法_Java单例模式相关推荐

  1. java 单例写法_java 单例模式的几种写法

    一.懒汉式 public classSingleton{private static Singleton instance = null;privateSingleton(){}public stat ...

  2. java单例设计模式_Java设计模式之单例模式详解

    在Java开发过程中,很多场景下都会碰到或要用到单例模式,在设计模式里也是经常作为指导学习的热门模式之一,相信每位开发同事都用到过.我们总是沿着前辈的足迹去做设定好的思路,往往没去探究为何这么做,所以 ...

  3. java 单例类_Java单例类

    单例类: 主要知识点: 1,单例类概念.特点 2,三种单例类懒汉,饿汉,双重加锁举例, 3,懒汉.饿汉区别以及单例类的总结: 1,概念:java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单 ...

  4. java单例枚举_Java增强枚举的用例

    java单例枚举 Brian Goetz在消息" 增强枚举-用例 "中写道:"我们希望就现在实现的功能[ 增强枚举 ]获得用户反馈." 他陈述了他的消息的第一个 ...

  5. java 单例方法,java单例模式使用及注意事项

    1. 说明 1)单例模式:确保一个类只有一个实例,自行实例化并向系统提供这个实例 2)单例模式分类:饿单例模式(类加载时实例化一个对象给自己的引用),懒单例模式(调用取得实例的方法如getInstan ...

  6. java单例代码_java中的单例模式的代码怎么写

    单例模式在我们日常的项目中十分常见,当我们在项目中需要一个这样的一个对象,这个对象在内存中只能有一个实例,这时我们就需要用到单例. 一般说来,单例模式通常有以下几种: 1.饥汉式单例 public c ...

  7. java+单例+恶汉_Java设计模式之单例模式(恶汉式和懒汉式)

    /** 单例模式:* 饿汉式:类一加载就创建对象* 懒汉式:用的时候,才去创建对象* 面试题:单例模式的思想是什么?写一个代码体现(我们最好写懒汉式的单例模式给面 /* * 单例模式: *       ...

  8. java 单例 实现_java 实现单例的各种方式

    概述 上一篇日志中,我们介绍了单例模式的概念和基础的应用 本节中,我们就来介绍一下 java 语言中如何编写单例模式类 只适合单线程环境的单例模式 public class Singleton { p ...

  9. java 单例 性能_java程序性能优化之设计优化---单例pk

    对于单例,很多人就要问了.为什么要使用单例,单例意义何在? 单例的产生是由于类的频繁使用,每次生成对象都要new,使用完值后GC要释放对象.这样一来系统性能降低,GC承受着巨大的压力.为了能够提升系统 ...

最新文章

  1. 为什么单片机程序中会有延时程序加入
  2. Qt之QHeaderView排序
  3. Leetcode--713. 乘积小于k的子数组
  4. 2022经营新增长第一课
  5. 数字反转(升级版)(洛谷-P1553)
  6. java url类下载_Java根据url下载图片或文件的工具类-Fun言
  7. cvs数据导入工具 oracle_oracle数据库的导入导出
  8. 在DSP28335上使用RTOS的经验总结
  9. java编程思想.pdf,阿里开发手册.pdf,MySQL是怎样运行的_掘金小册_.pdf
  10. mysql 插入语句语法_SQL 插入数据(INSERT INTO 语句)
  11. 印刷点阵字体_印刷术—类型族,分类和组合字体
  12. android sd卡数据恢复软件下载,手机SD卡内存卡数据恢复软件
  13. 《软件工程导论》考试复习题集锦
  14. wps中论文标题编号的设置
  15. LaTex公式使用(Word中的公式编辑,尤其是方程组等联合公式)
  16. 通达OA任意文件上传/文件包含RCE漏洞分析
  17. 提供云媒体服务器图片,云开发 把媒体文件上传到微信服务器 已知报错
  18. 微服务网关:SpringCloud Gateway保姆级入门教程
  19. Android手机ROM定制初级教程以及Android系统文件夹结构介绍【转】
  20. 矩形的对角线经过的小方格数量

热门文章

  1. python 异常处理相关说明
  2. 【Java】Stream流和方法引用
  3. winfrom导出DataGridView为Excel方法
  4. 动态通过网络获取json来tabbar图片和文字或其他信息
  5. 在MyEclipse中更换或修改svn的用户名和密码
  6. CSS样式如何解决IE浏览器不同版本的兼容问题
  7. MySQL Connector/ODBC 5.2.4 发布
  8. ios10前台收到推送_iOS 13 beta 2 推送 | iOS 13 热门疑问解答
  9. linux open 头文件_linux下通过共享内存和mmap实现进程间通讯
  10. 关于python2到python3更新的一些书写规则的更改