Singleton最熟悉不过了,下面学习单例模式。转载:http://zz563143188.iteye.com/blog/1847029

单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。

2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

1.简单的单例

package com.test.java.designPattern.singleton;

/**
* 单例模式
* Created by mrf on 2016/2/27.
*/
public class Singleton { /*持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载*/
private static Singleton instance = null; /*私有构造方法,防止被实例化*/
private Singleton(){
} /*静态工程方法,创建实例*/
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
} /*如果该对象用于序列化,可以保证对象在序列化前后保持一致*/
public Object readResolve(){
return instance;
}
}

这个类可以满足基本需求,但有线程安全问题,在多线程环境下,getInstance可能会创建多个。解决很简单,直接加synchronized就可以了:

public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}

但是,synchronized关键字锁住的是这个对象,这样的用法在性能上会有所下降,因为每次调用getInstance都要在对象上锁,事实上,只有第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方可以改进。如下:

    public static synchronized Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}

这样似乎解决了之前提到的问题,将synchronized关键字加到了内部(注意上锁的是类对象而不是实例对象instance,否则nullpoint),也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能上有一定的提升。但是,这样的情况,还是有可能有问题的。看下面的情况:

在Java指令中创建对象赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:

  1. A、B线程同时进入了第一个if判断
  2. A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
  3. 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
  4. B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
  5. 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

然而,我大概测试了几次,有时候会初始化2次甚至3次,代码:

/**
* 单例模式
* Created by mrf on 2016/2/27.
*/
public class Singleton { /*持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载*/
private static Singleton instance = null; public int i = 0; /*私有构造方法,防止被实例化*/
private Singleton(){
} /*静态工程方法,创建实例*/
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
System.out.println("创建初始化=================================================================================");
instance = new Singleton();
}
}
return instance;
} public void testInstance(){
// instance.i = 2;
} /**结果:
* 我测试了多次,有时候会初始化两次
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 1000000; i++) {
new Thread(new Runnable() {
public void run() {
Singleton instance = Singleton.getInstance();
instance.testInstance();
}
}).start();
}
} /*如果该对象用于序列化,可以保证对象在序列化前后保持一致*/
public Object readResolve(){
return instance;
} }

结果:

创建初始化=================================================================================
创建初始化=================================================================================

所以,程序还是可能发生错误,其实程序运行过程是很复杂的,从这点我们就可以看出,尤其是在写多线程环境下的程序更难。内存模型允许所谓的“无序写入”,这也是失败的一个主要原因。下一步,再优化:

单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式:

/**
* 改进,因为jvm创建instance这个实例也会出问题,创建变量和赋值是两个操作,顺序不定。
*/
class Singleton2{ /*私有构造方法,防止被实例化*/
private Singleton2(){
} /*此处使用一个内部类来维护单例*/
private static class SingletonFactory{
private static Singleton2 instance = new Singleton2();
}
/*获取实例*/
public static Singleton2 getInstance(){
return SingletonFactory.instance;
} /*如果该对象用于序列化,可以保证对象在序列化前后保持一致*/
public Object readResolve(){
return getInstance();
} }

其实说它完美,也不一定,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。也有人这样实现:因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字,也是可以的:

/**
* 安全的实现了单例
*/
class Singleton3{
private static volatile Singleton3 instance = null;
public int i; private Singleton3(){
} private static synchronized void syncInit(){
if(instance == null){
System.out.println("Singleton3创建初始化======================================================");
instance = new Singleton3();
}
} public static Singleton3 getInstance(){
if(instance ==null){
syncInit();
}
return instance;
}
public void testInstance(){
}
/**结果:
* 我测试了多次没发现问题
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 1000000; i++) {
new Thread(new Runnable() {
public void run() {
Singleton3 instance = Singleton3.getInstance();
instance.testInstance();
}
}).start();
}
}
}

上述方法在effective java中就是double check,因为前一种的同步使得每次获取实例时都要上锁,事实上只有第一次才需要上锁。那么双重检查,第一个if null可以过滤掉非初始化的操作,在初始化时上锁再检查if null。因此,代码也可以这样写:

/**
* double check
*/
class Singleton4{
private volatile static Singleton4 instance = null; private Singleton4() { } public static Singleton4 getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton4();
}
}
return instance;
}
}

  

2.补充:采用"影子实例"的办法为单例对象的属性同步更新

/**
* 影子实例:为单例对象的属性同步更新
*/
class SingletonTest{
private static SingletonTest instance = null;
private Vector properties = null; public Vector getProperties(){
return properties;
} private SingletonTest(){
} private static synchronized void synInit(){
if (instance ==null){
instance = new SingletonTest();
}
} public static SingletonTest getInstance(){
if(instance == null){
synInit();
}
return instance;
} public void updateProperties(){
SingletonTest shadow = new SingletonTest();
properties = shadow.getProperties();
}
}

通过单例模式的学习告诉我们:

  1. 单例模式理解起来简单,但是具体实现起来还是有一定的难度。
  2. sinchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用所的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。

到这里,单例模式基本学习完毕,结尾处,有个问题:就是采用类的静态方法实现单例模式的效果也是可行的,此处二者有什么不同?

首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态,因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)。

其次,单例可以被延迟初始化,静态类一般在第一次加载就初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。

再次,单例类可以被继承,他的方法可以被覆盖重写。但静态类的内部方法都是static,无法重写。

最后一点,单例类比较灵活,毕竟从实现上只是一个普通的java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其他功能,但是静态类不行。从上面这些概括中,基本可以看出二者的区别,但是从另一方面讲,我们上面最后实现的那个单例模式,内部就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。两种思想结合,才能造就出完美的解决方案,就像HashMap采用数据+链表来实现一样,其他生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美的方法是结合各个方法的优点。

java设计模式(四)--单例模式的更多相关文章

  1. Java设计模式之单例模式(七种写法)

    Java设计模式之单例模式(七种写法) 第一种,懒汉式,lazy初始化,线程不安全,多线程中无法工作: public class Singleton { private static Singleto ...

  2. Java 设计模式之单例模式(一)

    原文地址:Java 设计模式之单例模式(一) 博客地址:http://www.extlight.com 一.背景 没有太多原由,纯粹是记录和总结自己从业以来经历和学习的点点滴滴. 本篇内容为 Java ...

  3. java设计模式之单例模式你真的会了吗?(懒汉式篇)

    java设计模式之单例模式你真的会了吗?(懒汉式篇) 一.什么是单例模式? 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供 ...

  4. java 设计模式之单例模式

    -------Success is getting what you want, happiness is wanting what you get. java设计模式之单例模式(Singleton) ...

  5. 折腾Java设计模式之单例模式

    博文原址:折腾Java设计模式之单例模式 单例模式 Ensure a class has only one instance, and provide a global point of access ...

  6. java设计模式1——单例模式

    java设计模式1--单例模式 1.单例模式介绍 1.1.核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点 1.2.常见场景 1.3.单例模式的优点 1.4.常见的五种单例模式实现 ...

  7. java设计模式之单例模式(七种方法)

    单例模式:个人认为这个是最简单的一种设计模式,而且也是在我们开发中最常用的一个设计模式. 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个 ...

  8. [java设计模式]之单例模式

    -------------------此部分比較深入地解说了单例模式,原文链接已给出.兴许将涉及一些常见面试问题--------------------------- 原文地址:http://www. ...

  9. 深入探索Java设计模式之单例模式

    单例模式可确保在给定的时间实例中只能创建一个具有全局访问点的对象.这是面向对象编程中最常用的技术之一.尽管它很简单,但从类设计的角度来看可能是最简单的,但是在尝试实现它们之前,必须先解决一些细微的问题 ...

  10. java设计模式之单例模式(几种写法及比较)

    概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建 ...

随机推荐

  1. 初学python里的yield send next

    今天看书的时候突然看到这个想起来一直没有怎么使用过send和next试了一下 发现了一个诡异的问题 import math def get_primes(start): while 1 : if is ...

  2. storysnail的Linux串口编程笔记

    storysnail的Linux串口编程笔记 作者 He YiJun – storysnail<at>gmail.com 团队 ls 版权 转载请保留本声明! 本文档包含的原创代码根据Ge ...

  3. HTML5按钮的点击态问题

    开始在网页上实现点击态是mousedown mouseup来实现但是手机HTML5实现点击态怎么就不可以了呢 经过查资料才知道手机浏览器来实现点击态是通过 touchstart touchend实现

  4. Microsoft .NET Native Developer Preview 内部初探(1)

    Microsoft .NET Native Developer Preview 内部初探(1) MS 前段时间发布了.NET Native Developer Preview,被广大人员赋予“C++的 ...

  5. SQL SERVER 2012 只能识别20个CPU的问题

    背景 最近在给客户优化时时候发现客户的虚拟机环境分配了32C 但是只能识别20个,物理机64C,64G内存,本来没什么,CPU利用率在40%左右,但是优化后同时增加了 CPU和内存的分配,CPU32增 ...

  6. Rabbitmq基本原理

    MQ全称为Message Queue, 是一种分布式应用程序的的通信方法,它是消费-生产者模型的一个典型的代表,producer往消息队列中不断写入消息,而另一端consumer则可以读取或者订阅队列 ...

  7. DeviceOne接受了DevStore的专访

    DevStore隶属深圳尺子科技有限公司,DevStore所有内容(资讯.数据.工具|服务.资源文章.问答……)都贯穿整个项目的生命周期,无论你是设计人员.开发人员.推广人员还是运营人员都可在此找到你 ...

  8. java提高篇(二一)-----ArrayList

    一.ArrayList概述       ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List ...

  9. 使用SqlBulkCopy类来批量复制数据

    DataTable dt = new DataTable(); dt.Columns.Add("id", typeof(string)); ; i < ; i++) { Da ...

  10. 大叔也说Xamarin~Android篇~原生登陆与WebView的网站如何共享Session

    回到目录 事情是这样的,我们最近开了一个APP,主要使用xamarin做了一个登陆,它与服务器API进行数据通讯,当用户名密码正确去,跳转到新的activity,并在webview控件中打开服务端的H ...