一.问题引入

  偶然想想到的如果把Java的构造方法弄成private,那里面的成员属性是不是只有通过static来访问呢;如果构造方法是private的话,那么有什么好处呢;如果构造方法是private的话,会不更好的封装该内呢?我主要是应用在使用普通类模拟枚举类型里,后来发现这就是传说中的单例模式。构造函数弄成private 就是单例模式,即不想让别人用new 方法来创建多个对象,可以在类里面先生成一个对象,然后写一个public static方法把这个对象return出去。(eg:public 类名 getInstancd(){return 你刚刚生成的那个类对象;}),用static是因为你的构造函数是私有的,不能产生对象,所以只能用类名调用,所有只能是静态函数。成员变量也可以写getter/setter供外界访问的。如果谁要用这个类的实例就用有兴趣的读者参看我的这一篇博文http://www.cnblogs.com/hxsyl/archive/2013/03/18/2966360.html。

  第一个代码不是单例模式,也就是说不一定只要构造方法是private的就是单例模式。

class A(){private A(){}public name;pulbic static A creatInstance(){return new A();}}A a = A.createInstance();
a.name; //name 属性

public class single{ private static final single s=new single(); private single(){ } public static single getInstance(){ return s; }
}

二.单例模式概念及特点

  java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。
  单例模式有一下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。

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

  正是由于这个特 点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或 文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。

三.典型例题

  首先看一个经典的单例实现。

public class Singleton {private static Singleton uniqueInstance = null;private Singleton() {// Exists only to defeat instantiation.
 }public static Singleton getInstance() {if (uniqueInstance == null) {uniqueInstance = new Singleton();}return uniqueInstance;}// Other methods...

}

 Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

  但是以上实现没有考虑线程安全问题。所谓线程安全是指:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。显然以上实现并不满足线程安全的要求,在并发环境下很可能出现多个Singleton实例。

public class TestStream {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;} //该类只能有一个实例private TestStream(){}    //私有无参构造方法//该类必须自行创建//有2种方式/*private static final TestStream ts=new TestStream();*/private static TestStream ts1=null;//这个类必须自动向整个系统提供这个实例对象public static TestStream getTest(){if(ts1==null){ts1=new TestStream();}return ts1;}public void getInfo(){System.out.println("output message "+name);}}

public class TestMain {public static void main(String [] args){TestStream s=TestStream.getTest();s.setName("张孝祥");System.out.println(s.getName());TestStream s1=TestStream.getTest();s1.setName("张孝祥");System.out.println(s1.getName());s.getInfo();s1.getInfo();if(s==s1){System.out.println("创建的是同一个实例");}else if(s!=s1){System.out.println("创建的不是同一个实例");}else{System.out.println("application error");}}}

运行结果:
  张孝祥
  张孝祥
  output message 张孝祥
  output message 张孝祥
  创建的是同一个实例
 
结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。

  其次,下面是单例的三种实现。    

    1.饿汉式单例类

  飞哥下面这个可以不加final,因为静态方法只在编译期间执行一次初始化,也就是只会有一个对象。

//饿汉式单例类.在类初始化时,已经自行实例化 public class Singleton1 {//私有的默认构造子private Singleton1() {}//已经自行实例化 private static final Singleton1 single = new Singleton1();//静态工厂方法 public static Singleton1 getInstance() {return single;}}

2.懒汉式单例类

  那个if判断确保对象只创建一次。

//懒汉式单例类.在第一次调用的时候实例化 public class Singleton2 {//私有的默认构造子private Singleton2() {}//注意,这里没有final    private static Singleton2 single=null;//静态工厂方法 public synchronized  static Singleton2 getInstance() {if (single == null) {  single = new Singleton2();}  return single;}}

3.登记式单例类

import java.util.HashMap;import java.util.Map;//登记式单例类.//类似Spring里面的方法,将类名注册,下次从里面直接获取。public class Singleton3 {private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();static{Singleton3 single = new Singleton3();map.put(single.getClass().getName(), single);}//保护的默认构造子protected Singleton3(){}//静态工厂方法,返还此类惟一的实例public static Singleton3 getInstance(String name) {if(name == null) {name = Singleton3.class.getName();System.out.println("name == null"+"--->name="+name);}if(map.get(name) == null) {try {map.put(name, (Singleton3) Class.forName(name).newInstance());} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}return map.get(name);}//一个示意性的商业方法public String about() {    return "Hello, I am RegSingleton.";    }    public static void main(String[] args) {Singleton3 single3 = Singleton3.getInstance(null);System.out.println(single3.about());}}

四.单例对象作配置信息管理时可能会带来的几个同步问题
  

  1.在多线程环境下,单例对象的同步问题主要体现在两个方面,单例对象的初始化和单例对象的属性更新。

    本文描述的方法有如下假设:

    a. 单例对象的属性(或成员变量)的获取,是通过单例对象的初始化实现的。也就是说,在单例对象初始化时,会从文件或数据库中读取最新的配置信息。

    b. 其他对象不能直接改变单例对象的属性,单例对象属性的变化来源于配置文件或配置数据库数据的变化。

    1.1单例对象的初始化

      首先,讨论一下单例对象的初始化同步。单例模式的通常处理方式是,在对象中有一个静态成员变量,其类型就是单例类型本身;如果该变量为null,则创建该单例类型的对象,并将该变量指向这个对象;如果该变量不为null,则直接使用该变量。   

      这种处理方式在单线程的模式下可以很好的运行;但是在多线程模式下,可能产生问题。如果第一个线程发现成员变量为null,准备创建对象;这是第二 个线程同时也发现成员变量为null,也会创建新对象。这就会造成在一个JVM中有多个单例类型的实例。如果这个单例类型的成员变量在运行过程中变化,会 造成多个单例类型实例的不一致,产生一些很奇怪的现象。例如,某服务进程通过检查单例对象的某个属性来停止多个线程服务,如果存在多个单例对象的实例,就 会造成部分线程服务停止,部分线程服务不能停止的情况。

    1.2单例对象的属性更新

      通常,为了实现配置信息的实时更新,会有一个线程不停检测配置文件或配置数据库的内容,一旦发现变化,就更新到单例对象的属性中。在更新这些信 息的时候,很可能还会有其他线程正在读取这些信息,造成意想不到的后果。还是以通过单例对象属性停止线程服务为例,如果更新属性时读写不同步,可能访问该 属性时这个属性正好为空(null),程序就会抛出异常。

      下面是解决方法

//单例对象的初始化同步
public class GlobalConfig {private static GlobalConfig instance = null;private Vector properties = null;private GlobalConfig() {//Load configuration information from DB or file//Set values for properties
    }private static synchronized void syncInit() {if (instance == null) {instance = new GlobalConfig();}}public static GlobalConfig getInstance() {if (instance == null) {syncInit();}return instance;}public Vector getProperties() {return properties;}}

这种处理方式虽然引入了同步代码,但是因为这段同步代码只会在最开始的时候执行一次或多次,所以对整个系统的性能不会有影响。

  单例对象的属性更新同步。

  参照读者/写者的处理方式,设置一个读计数器,每次读取配置信息前,将计数器加1,读完后将计数器减1.只有在读计数器为0时,才能更新数据,同时要阻塞所有读属性的调用。

  代码如下:

public class GlobalConfig {private static GlobalConfig instance;private Vector properties = null;private boolean isUpdating = false;private int readCount = 0;private GlobalConfig() {//Load configuration information from DB or file//Set values for properties
 }private static synchronized void syncInit() {if (instance == null) {instance = new GlobalConfig();}}public static GlobalConfig getInstance() {if (instance==null) {syncInit();}return instance;}public synchronized void update(String p_data) {syncUpdateIn();//Update properties
 }private synchronized void syncUpdateIn() {while (readCount > 0) {try {wait();} catch (Exception e) {}}}private synchronized void syncReadIn() {readCount++;}private synchronized void syncReadOut() {readCount--;notifyAll();}public Vector getProperties() {syncReadIn();//Process data
  syncReadOut();return properties;}}

采用"影子实例"的办法具体说,就是在更新属性时,直接生成另一个单例对象实例,这个新生成的单例对象实例将从数据库或文件中读取最新的配置信息;然后将这些配置信息直接赋值给旧单例对象的属性。

public class GlobalConfig {private static GlobalConfig instance = null;private Vector properties = null;private GlobalConfig() {//Load configuration information from DB or file//Set values for properties
    }private static synchronized void syncInit() {if (instance = null) {instance = new GlobalConfig();}}public static GlobalConfig getInstance() {if (instance = null) {syncInit();}return instance;}public Vector getProperties() {return properties;}public void updateProperties() {//Load updated configuration information by new a GlobalConfig objectGlobalConfig shadow = new GlobalConfig();properties = shadow.getProperties();}}

注意:在更新方法中,通过生成新的GlobalConfig的实例,从文件或数据库中得到最新配置信息,并存放到properties属性中。上面两个方法比较起来,第二个方法更好,首先,编程更简单;其次,没有那么多的同步操作,对性能的影响也不大。

五.结束语

  参考了很多资料才整理出了该内容,请大家多多指教,期间受益颇多,感觉到OS很重要,等把网络版的俄罗斯方块搞好后就去复习OS……加油,我是最棒的!!!

  参考文献:http://www.cnblogs.com/whgw/archive/2011/10/05/2199535.html,http://blog.csdn.net/leesidong/article/details/6024455

转载于:https://www.cnblogs.com/dragon1013/p/5036027.html

Java学习-----单例模式相关推荐

  1. 我的WEB之路(一)-2.JAVA学习路线

    第一阶段:针对性夯实JAVA基础 课程名称 核心要点 完成目标 Java基础入门 搭建Java开发和运行环境等,IDE工具的学习和使用,Java语言基础,数据类型,运算符,条件和循环,数组使用等,Ja ...

  2. 转:Java学习路线图,专为新手定制的Java学习计划建议

    转自:http://blog.csdn.net/jinxfei/article/details/5545874 怎么学习Java,这是很多新手经常会问我的问题,现在我简单描述下一个Java初学者到就业 ...

  3. Java学习计划,给自己一个学习线路

    动力节点Java培训最新上线Java实验班,等你来测试自己适不适合学习Java编程哦! Java学习计划,给自己一个学习线路.语言是学习成本比较低的,Java作为一种高级语言,其相关语法相对简单,各种 ...

  4. Java学习教程整理

    Java 在编程语言排行榜中一直位列前排,可知 Java 语言的受欢迎程度了. 网上有很多 Java 教程,无论是基础入门还是开发小项目的教程都比比皆是,可是系统的很少,对于Java 学习者来说找到系 ...

  5. javame学习_从零基础自学Java教程:648集全网最新Java学习教程,一学就会

    我们都知道Java的功能非常的强大,Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由Sun Microsystems公司于1995年5月推出的Java程序设计语言和Java平台(即J ...

  6. 用java创建一个单例模式,采用Java实现单例模式

    一. 背景 单例模式是指在内存中只会创建且仅创建一次对象的设计模式.在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用 ...

  7. Java学习(入门知识)

    Java学习 java入门学习当你们还在打完一局王者的时候,我就已经开始入门java了哈哈哈哈哈哈 这是好久之前总结的了,先试着发一发吧. 1.类变量 类变量也称为静态变量 静态变量随着类的创建而创建 ...

  8. 寒假宅喵java学习

    1.25 小fleeting 学习java 关于辅助书籍: Core Java 字典 比如学习完了IO的知识,觉得意犹未尽,那么就通过这本字典一样的Java书籍,把IO的知识更丰满的刷一遍 Effec ...

  9. Java学习路线图分享(含项目+面试提升)最全整理

    为大家整理了2022最新的Java学习路线图,从学前准备.学习路线.项目实战.面试提升等四个方面给大家分享一些经验,希望可以给学习Java的小伙伴们一些帮助. 个人学习经验: 我本人是计算机科班出身, ...

最新文章

  1. 分布式存储系统的关键技术-针对应用和负载的存储优化技术
  2. css3的3d起步——分享
  3. Py之demjson:Python库之demjson的简介、安装、使用方法详细攻略
  4. HDU - 3341 Lost's revenge(AC自动机+状压dp)
  5. php yaf smarty,Yaf 结合用户自定义的视图(模板)引擎Smarty(Yaf + Smarty)
  6. 能留住女友的,都是王者
  7. omcat 7 的domain域名配置,Tomcat 修改JSESSIONID
  8. ubuntu无法进入图形界面,开机重复进入gun grup
  9. LeetCode347. 前 K 个高频元素(含详细解析JAVA实现)
  10. 《认知与设计——理解UI设计准则》系列笔记目录
  11. 华为独家承建波兰P4 UMTS网络
  12. 2家上市,4家排队,游戏公司今年为何偏爱港股?
  13. 三入职场 - 你可以从我身上学到这些(附毕业Vlog)
  14. 写出转heif的代码
  15. NumPy的矩阵表示方法
  16. org.springframework.context.annotation.ConflictingBeanDefinitionException异常处理
  17. 女工程师独家揭秘:双11秘密武器阿里云数据库团队故事
  18. 碎碎念No.01 你是个自信的人嘛
  19. 黑莓将入华,服务费用不菲
  20. access 江苏计算机二级_在江苏计算机二级证书是省级的好还是国家级的好?

热门文章

  1. git log 查看提交记录,参数:
  2. Cordova for iOS[ PhoneGap]
  3. 小米开源文件管理器MiCodeFileExplorer-源码研究(8)-文件排序工具类FileSortHelper
  4. CentOS安装mariadb
  5. OpenCV的示例程序在哪里?
  6. andorid简单计算器java源码_Android之一个简单计算器源代码
  7. timestamp(6) oracle计算差值_Oracle 计算两个时间的差值
  8. angular集成websocket_Angular + Websocket
  9. 电脑日常故障及处理(二)
  10. MySQL 8.0新特性--CTE Recurive(二)