java单例模式的七种写法_Java设计模式之单例模式的七种写法

什么是单例模式?

单例模式是一种常见的设计模式,单例模式的写法有很多种,这里主要介绍三种: 懒汉式单例模式、饿汉式单例模式、登记式单例 。

单例模式有以下特点:

1、单例类只能有一个实例。

2、单例类必须自己创建自己唯一的实例。

3、单例类必须给所有其它对象提供这一实例。

单例模式确保某各类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能,每台计算机可以有若干个打印机,但只能有一个Printer spooler,以避免两个打印作业同时输出到打印机中,每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态.

在将单例之前,要做一次基础知识的科普行动,大家都知道Java类加载器加载内容的顺序:

1、从上往下(Java的变量需要声明才能使用)

2、先静态后动态(对象实例化) (静态块和static关键字修饰在实例化以前分配内存空间)

3、先属性后方法(成员变量不能定义在方法中,只能定义在class下)

懒汉式单例(4种写法)

懒汉式顾名思义:需要用到的时候才会初始化

饿汉式:不管用不用先实例化

注册登记式:相当于有一个容器装载实例,在实例产生之前会先检查一下容器看有没有,如果有就直接取出来使用,如果没有就先new一个放进去,然后在后面的人使用,Spring IOC就是一种典型的注册登记单例

第一种写法:

/**

* Created by xingyuchao on 2018/1/20.

* 懒汉式单例类,在第一次使用的时候实例化自己

*/

public class Singleton {

//1.第一步先将构造方法私有化

private Singleton(){}

//2.然后声明一个静态变量保存单例的引用

private static Singleton single = null;

//3.通过提供一个静态方法来获得单例的引用

public static Singleton getInstance(){

if(single == null){

single = new Singleton();

}

return single;

}

}

Singleton1通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Signleton1的唯一实例只能通过getInstance()方法访问。

事实上,通过Java反射机制是能否实现实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效,此问题在此处不做讨论

但是以上懒汉式单例的实现没有考虑线程安全问题,它是非线程安全的,并发环境下可能出现多个Singleton1实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法的改造,保证了懒汉式单例的线程安全.

第二种写法:在getInstance()方法上加同步

/**

* Created by xingyuchao on 2018/1/29.

* 懒汉式单例类,保证线程安全

*/

public class Singleton2 {

//1.第一步先将构造方法私有化

private Singleton2(){}

//2.然后声明一个静态变量保存单例的引用

private static Singleton2 single = null;

//3.通过提供一个静态方法来获得单例的引用

// 为了保证线程环境下正确访问,给方法上加上同步锁synchronized

public static synchronized Singleton2 getInstance(){

if(single == null){

single = new Singleton2();

}

return single;

}

}

第三种写法:双重检测机制

/**

* Created by xingyuchao on 2018/1/29.

* 懒汉式单例类,保证线程安全 双重检测机制

*/

public class Singleton3 {

//1.第一步先将构造方法私有化

private Singleton3(){}

//2.然后声明一个静态变量保存单例的引用

private static Singleton3 single = null;

//3.通过提供一个静态方法来获得单例的引用

// 为了保证线程环境下的另一种实现方式,双重锁检查

public static synchronized Singleton3 getInstance(){

if(single == null){

single = new Singleton3();

}

return single;

}

}

第四种:静态内部类

/**

* Created by xingyuchao on 2018/1/29.

* 懒汉式单例类,通过静态内部类实现

*/

public class Singleton4 {

//1. 先声明一个静态内部类

//内部类的初始化,需要依赖主类

//也就是说,当JVM加载Singleton4类的时候LazyHolder类也会被加载

//只是目前还没有被实例化,需要等主类先实例化后,内部类才开始实例化

private static class LazyHolder{

//final是为了防止内部类将这个属性值覆盖掉

private static final Singleton4 INSTANCE = new Singleton4();

}

//2. 将默认构造方法私有化

private Singleton4(){}

//3.同样提供静态方法获取实例

//当getInstance方法第一次被调用的时候,它第一次读取LazyHolder.INSTANCE,内部类LazyHolder类得到初始化;

// 而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton4的实例,由于是静态的域,因此只会在

// 虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。这个模式的优势在于,getInstance方法并没有被同步,

// 并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

//此处加final是为了防止子类重写父类方法

public static final Singleton4 getInstance(){

return LazyHolder.INSTANCE;

}

}

饿汉式单例(1种写法)

/**

* Created by xingyuchao on 2018/1/29.

* 饿汉式单例类,在类初始化时,已经自行初始化,不会产生线程安全问题

*/

public class Singleton5 {

//1.同样也是将默认构造方法私有化

private Singleton5(){}

//2.声明静态变量,在类初始化之前就初始化变量,将对象引用保存

//相反的如果这个单例对象一直没使用,那么内存空间也就被浪费掉了

private static final Singleton5 singleton = new Singleton5();

//3.开放静态方法,获取实例

public static Singleton5 getSingleton(){

return singleton;

}

}

枚举式单例(1种写法)

public class DBConnection {}

/**

* Created by xingyuchao on 2018/1/29.

* 枚举式单例

*/

public enum Singleton6 {

DATASOURCE;

private DBConnection connection = null;

private Singleton6() {

connection = new DBConnection();

}

public DBConnection getConnection() {

return connection;

}

}

/**

* Created by xingyuchao on 2018/1/29.

*/

public class Main {

public static void main(String[] args) {

DBConnection dbConnection1 = Singleton6.DATASOURCE.getConnection();

DBConnection dbConnection2 = Singleton6.DATASOURCE.getConnection();

System.out.println(dbConnection1 == dbConnection2); //true true结果表明两次获取返回了相同的实例。

}

}

这种方式不仅能避免多线程同步问题,而且能防止反射创建新的对象,可谓是很坚强的壁垒不过这种方式用的极少

为什么枚举会满足线程安全、序列化等标准。参考:http://blog..net/gavin_dyson/article/details/70832185

登记注册式单例

/**

* Created by xingyuchao on 2018/1/29.

* 登记式单例:类似spring里面的方法,将类名注册,下次直接从里面获取

*

* 登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记簿)中,对于已经登记过的实例,则从Map直接获取,对于没有登记的,则先登记,然后返回

*

* 内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在被装载的时候就被实例化了

*/

public class Singleton7 {

private static Map map = new HashMap<>();

static{

Singleton7 singleton7 = new Singleton7();

map.put(singleton7.getClass().getName(),singleton7);

}

//保护的默认构造

protected Singleton7(){}

//静态工程方法,返回此类的唯一实例

public static Singleton7 getInstance(String name) throws Exception {

if(name == null){

name = Singleton7.class.getName();

}

if(map.get(name) == null){

map.put(name,(Singleton7)Class.forName(name).newInstance());

}

return map.get(name);

}

}

测试:

public class Test {

public static void main(String[] args) throws Exception{

//启动100个线程同时去抢占cpu ,有可能产生并发,观察并发情况下是否为同一个对象实例

int count = 100;

//发令枪

CountDownLatch latch = new CountDownLatch(count);

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

//Lambda简化后

new Thread(()->{

System.out.println(System.currentTimeMillis() + ":" + Singleton4.getInstance());

}).start();

latch.countDown();

}

latch.await(); //开始法令,抢占cpu

}

}

结果:

610dd550cb6ae34f16c323aa5ff9d978.png

分布式环境下的单例

有两个问题需要注意:

1. 如果单例类由不同的类装载器装载,那边可能存在多个单例类的实例。假定不是远端存取,例如有一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就会有各自的实例

解决:指定classloader

private static Class getClass(String classname) throws ClassNotFoundException{

ClassLoader classloader = Thread.currentThread().getContextClassLoader();

if(classloader == null){

classloader = Singleton.class.getClassLoader();

}

return (classloader.loadClass(classname));

}

2. 如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎么样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那么就会有多个类的实例

public class Singleton implements Serializable {

public static Singleton singleton = new Singleton();

protected Singleton(){}

private Object readResolve(){

return singleton;

}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/399128.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在Hadoop集群上,搭建HBase集群

(1)下载Hbase包,并解压:这里下载的是0.98.4版本,对应的hadoop-1.2.1集群 (2)覆盖相关的包:在这个版本里,Hbase刚好和Hadoop集群完美配合,不需要进行覆盖. 不过这里写了个覆盖的脚本,可以留着备用. find -name hadoop*jar | sed s/2.2.0/2.3.0/g | sed s/.\///g > f.log rm .…

java js跳出循环_[Java教程]js循环的总结

[Java教程]js循环的总结02016-10-07 15:00:14js原生的循环有两种&#xff0c;一般的for循环和for...in循环。还有一种常用jQuery.each()循环。一. js原生循环a. for循环&#xff0c;代码如下&#xff1a;var myArray [1,2,3];for (var i 0; i < myArray.length; i) {conso…

Beta版本冲刺

1.凡事预则立&#xff0c;在Beta开始前&#xff0c;以小组为单位&#xff0c;在敏捷冲刺前发布一篇博客&#xff0c;描述&#xff1a; a. 下一阶段需要改进完善的功能&#xff1a; 无 b. 下一阶段新增的功能 修改密码功能 管理员功能 生成表格的优化 c. 需要改进的团队分工&…

Struts2国际化

一&#xff1a;简单理解 国际化简称i18n&#xff0c;其来源是英文单词 internationalization的首末字符i 和n。18为中间的字符数。 随着全球经济的一体化&#xff0c;软件开发者应该开发出支持多国语言、国际化的Web应用。对于Web应用来说&#xff0c;同样的页面在不同的语言环…

NOI经验谈

对于NOI来说&#xff0c;甚至比硬实力更加重要。我觉得一场考试这么几件事要做&#xff1a;看题&#xff0c;选题&#xff0c;分析&#xff0c;编码&#xff0c;调试&#xff0c;测试&#xff0c;骗分。 1、看题 拿到试卷以后的第一件事就是看题。看题不是看小说&#xff0c;要…

ReactiveCocoa源码拆分解析(四)

&#xff08;整个关于ReactiveCocoa的代码工程可以在https://github.com/qianhongqiang/QHQReactive下载&#xff09; 上一章节简要的说明了如何实现的热信号。但是像那么写&#xff0c;貌似不是非常优雅。这一章节我们会把冷热信号转换写的跟ReactiveCocoa一样优雅。 Reactive…

用Emacs编写mybatis

<?xml version"1.0" encoding"utf-8"?> 用Emacs编写mybatis用Emacs编写mybatis Table of Contents 1. 效果图2. 配置1 效果图 2 配置 现在web开发&#xff0c;最流行的orm框架非mybatis莫属了&#xff0c;它功能强大&#xff0c;编写简单灵活&…

lambda显式声明返回值

10.21 编写一个lambda&#xff0c;捕获一个局部int变量&#xff0c;并递减变量值&#xff0c;直至它变为0.一旦变量变为0&#xff0c;再调用lambda应该不再递减变量。lambda应该返回一个bool值&#xff0c;指出捕获的变量是否为0. #include<iostream> #include<algori…

【Codeforces Round #452 (Div. 2) C】 Dividing the numbers

【链接】 我是链接,点我呀:) 【题意】 在这里输入题意 【题解】 n为偶数。 l 1, r n (l,r)放在一组 l,r-- 新的l,r放在另外一组 直到l1r 这个时候,判断两组的和,如果一样的话,分散在两组 差为1否则差为0n为奇数 l 2,r n (l,r)放在一组 l,r-- 新的l,r放在另外一组 直到l1r…

java中reject方法作用_Java BindingResult.rejectValue方法代碼示例

本文整理匯總了Java中org.springframework.validation.BindingResult.rejectValue方法的典型用法代碼示例。如果您正苦於以下問題&#xff1a;Java BindingResult.rejectValue方法的具體用法&#xff1f;Java BindingResult.rejectValue怎麽用&#xff1f;Java BindingResult.r…

java方法参数

Java程序设计语言总是采用值调用。也就是说&#xff0c;方法得到的是所有参数的一个拷贝&#xff0c;特别是方法不能修改传递给它的任何参数变量的内容。 基本类型参数 1&#xff09;X被初始化为percent值的一个拷贝&#xff1b; 2&#xff09;X被乘以3等于30。但是percent仍然…

SaltStack源码分析之:master端执行salt模块大致流程

2019独角兽企业重金招聘Python工程师标准>>> ##JOB执行流程 先看下官网对于master端的工作流程的介绍&#xff1a; The Salt master works by always publishing commands to all connected minions and the minions decide if the command is meant for them by ch…

myecplise新建Maven项目Filter选什么,使用myeclipse建立maven项目

myecplise新建Maven项目Filter选什么 使用myeclipse建立maven项目 1234567分步阅读maven是管理项目的&#xff0c;myeclipse是编写代码的。第一次写项目都要配置好多东西&#xff0c;很麻烦&#xff0c;now 来看看怎样新建一个maven项目。 工具/原料 myeclipsemaven方法/步骤 1…

python参数传递时不构造新数据对象_关于函数的参数传递(parameter passing),以下选项中描述错误的是_学小易找答案...

【单选题】下面代码的输出结果是: a [] for i in range(2,10): count 0 for x in range(2,i-1): if i % x 0: count 1 if count 0: a.append(i) print(a)【单选题】Python3.0正式发布的年份是【单选题】以下选项中,对于函数的定义错误的是【单选题】关于函数的参数传递(pa…

关于TCP/IP与数据传输

一、TCP/IP的具体含义&#xff1a; 从字面意思来讲&#xff0c;很多人会认为TCP/IP是指TCP与IP这两种协议。有时确实也可以说是这两种协议&#xff0c;但是大部分情况下所说的是利用IP进行通信时所必须用到的协议群的统称。具体来说IP,ICMP,TCP,UDP,FTP以及HTTP等都属于TCP/IP协…

geohash php_空间索引-geohash算法实现

算法简介geohash是实现空间索引的一种算法,其他实现空间索引的算法有:R树和其变种GIST树、四叉树、网格索引等算法基本原理geohash算法将地球理解为一个二维平面&#xff0c;将平面递归分解成更小的子块&#xff0c;每个子块在一定经纬度范围内拥有相同的编码&#xff0c;这种方…

ActiveReports 报表控件V12新特性 -- 新增JSON和CSV导出

ActiveReports 报表控件V12新特性 -- 新增JSON和CSV导出 ActiveReports 是一款专注于 .NET 平台的报表控件&#xff0c;全面满足 HTML5 / WinForms / ASP.NET / ASP.NET MVC / WPF 等平台下报表设计和开发工作需求&#xff0c;作为专业的报表工具为全球超过 300,000 开发人员提…

Codeforces Round #112 (Div. 2)---A. Supercentral Point

Supercentral Pointtime limit per test2 secondsmemory limit per test256 megabytesinputstandard inputoutputstandard outputOne day Vasya painted a Cartesian coordinate system on a piece of paper and marked some set of points (x1, y1), (x2, y2), ..., (xn,…

php imap配置,怎么为PHP编译imap扩展?

为PHP编译imap扩展的方法&#xff1a;首先安装“imap-open2007e”&#xff1b;然后下载源代码&#xff1b;接着准备好系统的“imap-open”环境&#xff1b;最后进入“./ext/extension/imap/”文件夹下执行“make”命令即可。怎么为PHP编译imap扩展&#xff1f;最近为项目增加了…