`
5452
  • 浏览: 28873 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

静态类和单例的抉择

阅读更多
先说说我要实现的要求:我要一个Map,这个Map是常量,但是它要进行初始化,填充数据。而且这个Map要可以重用,实现优雅点的
public class ApproveMap {

	private static Map<String, String> approveCodeMap = new HashMap<String, String>();

	/**
	 * @return
	 */
	public static Map<String, String> getApprove() {
		synchronized (approveCodeMap) {
			if (approveCodeMap.isEmpty())
				setApprove();
		}
		return approveCodeMap;
	}

	private static void setApprove() {
		/**
		 *
		 */
		approveCodeMap.put("101102", "11_0003_0001");
		approveCodeMap.put("101103", "11_0003_0004");
		approveCodeMap.put("101104", "11_0003_0005");
		approveCodeMap.put("101105", "11_0003_0002");
		approveCodeMap.put("101106", "11_0003_0003");
		approveCodeMap.put("101107", "11_0003_0006");

	}
}


以上是我写的一个方式,总觉得别扭,大家一块看看有什么更好的方法。

2010年6月更新:
这个东西看着确实不是东西,有静态初始化块了,没必要有getter了,实在是。。。
public class ApproveMap {

	public static final Map<String, String> approveCodeMap = new HashMap<String, String>();
	static {
		/**
		 *
		 */
		approveCodeMap.put("101102", "11_0003_0001");
		approveCodeMap.put("101103", "11_0003_0004");
		approveCodeMap.put("101104", "11_0003_0005");
		approveCodeMap.put("101105", "11_0003_0002");
		approveCodeMap.put("101106", "11_0003_0003");
		approveCodeMap.put("101107", "11_0003_0006");

	}
}
分享到:
评论
40 楼 andot 2009-02-26  
找不到服务器 写道
初始化需要同步这个思想是正确的,但是不需要我们去显式的干这个事情,JVM会"自动加锁"

比如,线程T1调用某个类A,若A没有被加载,那么A会被JVM加载,如果在加载的过程中有其他线程T2也要调用A,T2那么只能等待,而不是再去加载。也许在加载过程中T1的时间片到期,但是T2还是会继续阻塞,直到A被加载完成。所以这个过程就好像JVM自动加锁了一样。。。。。。。。。。。


嗯,我那个测试程序就是来说明这一点的。我愿意为 JVM 不会“自动加锁”,可是测试结果表明 JVM 会“自动加锁”,所以表明我以前的想法是错的。
39 楼 找不到服务器 2009-02-26  
初始化需要同步这个思想是正确的,但是不需要我们去显式的干这个事情,JVM会"自动加锁"

比如,线程T1调用某个类A,若A没有被加载,那么A会被JVM加载,如果在加载的过程中有其他线程T2也要调用A,T2那么只能等待,而不是再去加载。也许在加载过程中T1的时间片到期,但是T2还是会继续阻塞,直到A被加载完成。所以这个过程就好像JVM自动加锁了一样。。。。。。。。。。。
38 楼 yyjn12 2009-02-26  
不管多少个并发访问,static的代码块都只会顺序执行其中的代码,只会执行一遍,那还用加什么 synchronize ?
37 楼 andot 2009-02-26  
yyjn12 写道
你这能测出什么来?
如果你怀疑类的static代码块会多次执行,那么你也可能会怀疑
private static HashMap<String, String> map = new HashMap<String, String>();
这个static的成员变量会不只有一个了。
于是你console输出的东西看不出结论。


我何时说过 static 代码块会多次执行这样的结论。你看清楚帖子在说话好不好?
36 楼 yyjn12 2009-02-26  
你这能测出什么来?
如果你怀疑类的static代码块会多次执行,那么你也可能会怀疑
private static HashMap<String, String> map = new HashMap<String, String>();
这个static的成员变量会不只有一个了。
于是你console输出的东西看不出结论。
35 楼 andot 2009-02-25  
icewubin 写道
andot 写道
经过实际测试发现,我原来认为的是错的。确实静态 static 块自己会加锁,不需要再手工用同步块加锁了。下面是测试代码:

            Logger.getLogger(MyStatic.class.getName()).log(Level.SEVERE, null, ex);


1.Logger.getLogger(MyStatic.class)就可以了。
2.static初始化,也就只能做做这些简单的初始化,复杂的初始化,会出现意想不到的问题的,不可靠的。


嗯,不过我也就做做这种简单的初始化,我想 static 应该足够了。最重要的是我那个程序不能依赖于 Spring(那怕提供跟 Spring 的集成能力,但是主要部分还是需要可以独立于 Spring 运行的),所以初始化就不能靠 Spring 或者 Web 服务器了。
34 楼 icewubin 2009-02-25  
andot 写道
经过实际测试发现,我原来认为的是错的。确实静态 static 块自己会加锁,不需要再手工用同步块加锁了。下面是测试代码:

            Logger.getLogger(MyStatic.class.getName()).log(Level.SEVERE, null, ex);


1.Logger.getLogger(MyStatic.class)就可以了。
2.static初始化,也就只能做做这些简单的初始化,复杂的初始化,会出现意想不到的问题的,不可靠的。
33 楼 lkjust08 2009-02-25  
这样实现也不错呀。得看看你在什么样的情况用?
32 楼 andot 2009-02-25  
经过实际测试发现,我原来认为的是错的。确实静态 static 块自己会加锁,不需要再手工用同步块加锁了。下面是测试代码:

package teststatic;

import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MyStatic {
    private static HashMap<String, String> map = new HashMap<String, String>();
    static {
        try {
            System.out.println("start init");
            map.put("101101", "11_0003_0007");
            Thread.sleep(1000);
            map.put("101102", "11_0003_0001");
            Thread.sleep(1000);
            map.put("101103", "11_0003_0004");
            Thread.sleep(1000);
            map.put("101104", "11_0003_0005");
            Thread.sleep(1000);
            map.put("101105", "11_0003_0002");
            Thread.sleep(1000);
            map.put("101106", "11_0003_0003");
            Thread.sleep(1000);
            map.put("101107", "11_0003_0006");
            System.out.println("end init");
        }
        catch (InterruptedException ex) {
            Logger.getLogger(MyStatic.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    public static String getData(String key) {
        return map.get(key);
    }
}

package teststatic;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i < 8; i++) {
            final int n = i;
            Runnable r = new Runnable() {
                public void run() {
                    System.out.println("start call" + n);
                    System.out.println(MyStatic.getData("10110" + n));
                    System.out.println("end call" + n);
                }
            };
            new Thread(r).start();
            Thread.sleep(50);
        }
    }
}

运行结果:
start call1
start init
start call2
start call3
start call4
start call5
start call6
start call7
end init
11_0003_0006
end call7
11_0003_0007
end call1
11_0003_0002
11_0003_0005
end call4
11_0003_0001
end call2
11_0003_0003
11_0003_0004
end call3
end call5
end call6
31 楼 txzyhm521 2009-02-25  
静态内部类,避免静态代码块枷锁的
30 楼 yyjn12 2009-02-25  
虚拟机加载这个类的时候,就会先执行它的  static 代码块。
假如static代码块和main方法在同一个类中,那么static代码块甚至会比main方法更早执行,为什么呢?因为main方法要执行,得先由jvm 加载 这个类。

那个头像看起来很别扭的同学,你认为虚拟机会多次加载一个类吗?

我个人觉得虚拟机加载类只会加载一次。
jvm肯定会做这个事情,那么static代码块有什么必要同步呢?
未经证实,只是想象而已。
29 楼 icewubin 2009-02-25  
andot 写道
就是 PHPRPC for Java 中的那个 DHParams 类(静态类),static 块是用于初始化一个 HashMap,里面的数据来自于 jar 包中的 10 个左右的文件,这个类中的方法是在客户端向服务器端发起第一次加密调用时被访问到,之前不加锁,当多个客户端同时发起加密调用时,就会出现错误。加锁之后,就不会发生这种情况了。如果能够在 Web 服务器启动时载入当然更好一些,不过因为这个东西是不依赖于任何特定服务器的,所以不能作出任何假设。

1.先针对HashMap这种初始化,做一个特殊的应对方法。之前碰到这种“简单”的初始化,我也是用static代码块来解决的,原因是简单且几乎不需要测试正确性。后来改进了一下,看下面这个例子:
    private Map operationTypeMap = MapTool.assembleMap(new String[][] {
            {"开放", DictEnumCfg.OPERATION_TYPE_Open},
            {"封闭", DictEnumCfg.OPERATION_TYPE_Normal_Closed}
    });

public class MapTool {
    
    /**快速构造一个Map,Map example = MapTool.assembleMap(new String[][] {{"1", "好"}, {"2", "不好"}})
     * @param arrays 两层String数组
     * @return Map<String, String>
     */
    public static Map assembleMap(String[][] arrays) {
        Map map = new HashMap(arrays.length);
        for (int i = 0; i < arrays.length; i++) {
            map.put(arrays[i][0], arrays[i][1]);
        }
        return map;
    }
    
}

主要思想是使得初始化过程在operationTypeMap变量定义的时候初始化完毕,写起来方便省事。

2.在web.xml中配置初始化:
    <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

以这个为例,其中的load-on-startup中的数字就是初始化Servlet的顺序,利用J2EE服务器启动时初始化Servlet,就可以利用这个临时的Servlet来初始化你想要干的事情,比如在servlet中的init()方法中写一句:
Class.forName("com.javaeye.Driver");

文本的没有编译重构优势,也可以这么写:
com.javaeye.Driver.init();

假设把初始化代码放在了init方法中,说到这有点远了,请看3。

3.如果初始化代码复杂,这些都不是正解,Java中类的生命周期的管理,包括是否单例、是否初始化、初始化链的控制,资源的自动或手动注入,最好全部由Spring来控制,也不要依赖于任何J2EE容器(这能给测试带来极大的方便,测试的时候只要利用Spring的测试基类就能加载全部资源)。

例如Spring的bean默认就是单例的:
<bean id="driver" class="com.javaeye.Driver"/>


如果不是单例,那就是原型:
<bean id="driver" class="com.javaeye.Driver" singleton="false"/>

或(好像前一种可读性稍好)
<bean id="driver" class="com.javaeye.Driver" scope="prototype"/>


如果需要初始化,如下即可:
<bean id="driver" class="com.javaeye.Driver" init-method="init"/>

写一个非static的public的init方法,其中是初始化代码。

这样这个com.javaeye.Driver的代码本身是很简单的,没有任何什么所谓的单例或者static代码块,只有一个public的init方法,想怎么调用都行,当然最好是使用Spring来做bean的生命周期管理,写简单的JUnit测试或者基于Spring的测试框架,写JUnit也都是非常方便的。

最后总结一下,我认为Java中使用Spring来管理资源链,能极大的节省自己写单例或者Factory设计模式的代码,同时能够给测试的时候带来极大的灵活性,因为资源都是注入的,测试的时候,可以随意注入mock对象。
28 楼 ramus 2009-02-25  
private static Map map;
static{
  if(map==null){
       map=new HashMap();
           map.put("101102", "11_0003_0001"); 
           map.put("101103", "11_0003_0004"); 
           map.put("101104", "11_0003_0005"); 
           map.put("101105", "11_0003_0002"); 
           map.put("101106", "11_0003_0003"); 
           map.put("101107", "11_0003_0006"); 
   }
}


前提是map里面的参数是不会变化的
27 楼 icewubin 2009-02-25  
elvewyn 写道
感觉上, 很多时候。单利的比静态类更加易于测试。

正解,static代码块非常的不优雅,只能做做很简单的初始化,因为要针对static代码块写回归测试也不是很方便。

但是用了Spring后,单例和初始化自己都不用写了。
26 楼 andot 2009-02-24  
icewubin 写道
andot 写道
能不能说一下,Class.forName()在何时调用,就是说写在代码的哪个位置,对这个比较感兴趣,以前没接触过这个,所以一直都是在 static 块中加锁来保证线程不会冲突的,因为原来没加锁时,确实遇到过线程冲突的问题。就是在 Web 服务器上部署是遇到的。

这个。。。最好把你碰到的情况详细说一下(例如static代码块如果有依赖于访问数据库的操作,那是最好换一种思路了)。

一般来说web.xml中可以指定初始化的顺序(例子我现在没有,明天到单位才),比如有一个类叫做com.javaeye.Driver,那只要最优先触发一下Class.forName("com.javaeye.Driver")就可以了。

说管说,真要做的话,因为我使用Spring,使用Spring的初始化功能来做的(要例子的话明天给)。static只能做做简单的初始化,例如map的初始化等等,复杂的初始化最好不要用static代码块。


就是 PHPRPC for Java 中的那个 DHParams 类(静态类),static 块是用于初始化一个 HashMap,里面的数据来自于 jar 包中的 10 个左右的文件,这个类中的方法是在客户端向服务器端发起第一次加密调用时被访问到,之前不加锁,当多个客户端同时发起加密调用时,就会出现错误。加锁之后,就不会发生这种情况了。如果能够在 Web 服务器启动时载入当然更好一些,不过因为这个东西是不依赖于任何特定服务器的,所以不能作出任何假设。
25 楼 elvewyn 2009-02-24  
感觉上, 很多时候。单利的比静态类更加易于测试。
24 楼 icewubin 2009-02-24  
andot 写道
能不能说一下,Class.forName()在何时调用,就是说写在代码的哪个位置,对这个比较感兴趣,以前没接触过这个,所以一直都是在 static 块中加锁来保证线程不会冲突的,因为原来没加锁时,确实遇到过线程冲突的问题。就是在 Web 服务器上部署是遇到的。

这个。。。最好把你碰到的情况详细说一下(例如static代码块如果有依赖于访问数据库的操作,那是最好换一种思路了)。

一般来说web.xml中可以指定初始化的顺序(例子我现在没有,明天到单位才),比如有一个类叫做com.javaeye.Driver,那只要最优先触发一下Class.forName("com.javaeye.Driver")就可以了。

说管说,真要做的话,因为我使用Spring,使用Spring的初始化功能来做的(要例子的话明天给)。static只能做做简单的初始化,例如map的初始化等等,复杂的初始化最好不要用static代码块。
23 楼 andot 2009-02-24  
能不能说一下,Class.forName()在何时调用,就是说写在代码的哪个位置,对这个比较感兴趣,以前没接触过这个,所以一直都是在 static 块中加锁来保证线程不会冲突的,因为原来没加锁时,确实遇到过线程冲突的问题。就是在 Web 服务器上部署是遇到的。
22 楼 icewubin 2009-02-24  
andot 写道
icewubin 写道
andot 写道
icewubin 写道
andot 写道
静态初始化也要加锁,在多线程模式下,你不能保证在初始化之前不会被访问。

Java中的static代码块,不需要加锁的。


它是自动加锁的吗?

因为Java的类加载的特性,static代码块只会运行一次,所以不需要锁。

一般简单来说,当程序第一次调用到这个类的某个变量或方法(除了static final的变量),都算是首次加载这个类到内存中准备调用,但是调用之前,先初始化static的变量,然后执行static代码段(暂且认为static代码段在static变量定义之后),此时才算类加载完毕,再运行之前的调用请求。

当然后续再有其他调用请求,这个类加载的过程不会再执行一遍的。


如果是你说的这样的话,如果不加锁还是有可能会出问题的。

比如当第一次访问这个类的静态方法来获取HashMap中的值时,他会先执行static块,在执行static块的时候(没完成),如果另一个线程也同时调用这个类的静态方法来获取HashMap中的值时,就会有冲突了(比如还没有初始化完,得不到希望的值)。所以,就算在static块中初始化HashMap的元素,也还是加锁比较好。

不会的,JVM会保证在执行完static中的代码,才会执行被阻塞的调用,JVM的类加载机制是有同步功能的,利用此同步功能还有人创造性的设计出最为简洁的Java的单例模式的最佳实践:
http://icewubin.iteye.com/admin/blogs/256869

我再多说两句,如果static块中初始化代码执行时间较长,一般会使用一些技巧来提前触发类的加载使得static中的代码提前执行(比如JVM启动的时候或者J2EE服务器启动的时候),以避免真正调用时的阻塞或性能问题。提前触发的办法有不少,最正规的方式是使用Class.forName()方法。是不是很眼熟,就是JDBC的例子中的Class.forName(),它的作用并不仅仅是确认驱动类是否存在,真正的作用而是确保提前执行JDBC驱动类中的static代码块。
21 楼 丁丁豆 2009-02-24  
同意楼上的观点,这点很重要,影响你的效率

相关推荐

    QT静态单例管理信号和槽

    QT静态单例管理信号和槽是Qt框架中一种常见的设计模式,用于确保应用程序中只有一个特定类的实例。在Qt编程中,单例模式通常用于管理全局资源,如数据库连接、配置文件读取或系统设置。这里我们将深入探讨如何在Qt中...

    Java静态内部类实现单例过程

    在Java中,有多种实现单例的方式,包括饿汉式、懒汉式、双重检查锁定(DCL)、静态内部类和枚举。这里我们将重点讨论静态内部类和枚举实现单例的过程。 首先,让我们来看看静态内部类实现单例的方式: ```java ...

    单例模式中声明静态自己类型的指针编译显示未定义处理

    单例模式是软件设计模式中的一种,用于控制类的实例化过程,确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统中需要频繁创建和销毁的对象,或者需要共享资源的情况下非常有用。然而,实现单例模式时,...

    Java单例模式实现静态内部类方法示例

    在Java中,单例模式可以通过多种方式实现,包括懒汉式、饿汉式、双重检查锁定和静态内部类方法等。今天,我们主要介绍了Java单例模式实现静态内部类方法示例,涉及构造函数私有化等相关内容。 单例模式的定义 单例...

    Java 单例模式 工具类

    通过`SingletonFactory`工具类,我们不仅可以方便地使用各种单例模式,还可以对自定义的单例进行统一管理和访问,提高了代码的可维护性和灵活性。在实际开发中,应根据项目需求选择适合的单例实现方式,以保证代码的...

    joomla里面的单例模式和纯静态类

    在Joomla!涉及到了很多的单例模式,比如JFactory,JURI等等。 对于一个请求中需要一个对象实例的,joomla大多采用了单例模式,可以避免重复实例化带来的资源浪费和性能损耗。

    php单例模式实例

    单例模式的核心思想是限制类的实例化过程,确保在程序运行期间,类的实例只有一个。通过控制类的构造函数,使其不能被外部直接实例化,而是通过一个静态方法来获取唯一的实例。这样,无论何时何地,只要调用这个静态...

    Java中的静态变量静态方法静态块与静态类.docx

    Java 中的静态变量、静态方法、静态块和静态类 Java 中的静态变量、静态方法、静态块和静态类是 Java 编程语言的四个重要概念,它们之间存在着紧密的关系。下面将对这四个概念进行详细的介绍。 一、静态变量...

    设计模式之单例模式(结合工厂模式)

    此外,单例模式还有几种变体,比如静态内部类单例和枚举单例。静态内部类单例利用Java类加载机制保证了线程安全,而枚举单例则是Java中实现单例的最佳方式,因为它天然支持序列化且防止反射攻击。 在代码实现上,...

    java中的单例模式

    静态内部类单例利用JVM保证了类加载的线程安全性,而枚举单例则是一种既简洁又线程安全的实现方式,也是官方推荐的单例实现方式。 ```java public enum Singleton { INSTANCE; // ... } ``` 在使用单例模式时,...

    Java中的单例模式与静态类

    单例模式与静态类(一个类,所有方法为静态方法)是另一个非常有趣的问题,在《Java中有关单例模式的面试问题》博文中露掉了,由于单例模式和静态类都具有良好的访问性,它们之间有许多相似之处,例如,两者可以直接...

    java使用静态关键字实现单例模式

    在上面的代码中,我们定义了一个 SingletonMode 类,它有一个私有的构造方法和一个公有的静态方法 getInstance()。getInstance() 方法用于获取 SingletonMode 的实例,如果实例不存在,则创建一个新的实例。 测试...

    java单例设计模式 4中实现方式,重点介绍了静态内部类的实现方式

    静态内部类单例模式利用了Java语言中静态内部类的特性来实现单例模式。具体来说它将单例对象的创建延迟到静态内部类被加载时才执行,从而避免了多线程环境下的同步问题。下面是一个简单的示例代码: ```java public...

    java 设计模式 单例模式

    //单例模式,始终只产生一个对象 /*public class SingleTest { public static void main(String[] args) { SingleTon singleTon1=SingleTon.getInstance(); SingleTon singleTon2=SingleTon.getInstance(); ...

    这可能是最全的单例模式了

    静态内部类实现单例模式5. 饿汉实现单例模式6. 饿汉变种实现单例模式7. 枚举实现单例模式static修饰下是怎么做到线程安全的?完全不使用synchronized实现单例模式1. CAS(AtomicReference)实现单例模式2. ...

    singleton_crash:演示由多个动态库链接的静态库中的单例导致的崩溃

    然而,如果不正确地处理单例,特别是在涉及动态库和静态库的情况下,可能会导致各种问题,甚至引发程序崩溃。标题和描述所提到的"singleton_crash"就是这种情况的一个实例,它探讨了当静态库被多个动态库链接时,...

    静态内部类

    根据内部类是否声明为static,它们被分为非静态内部类(也称为成员内部类)和静态内部类。 #### 二、静态内部类的特点 静态内部类具有以下特点: 1. **独立性**:静态内部类与外部类之间没有依赖关系,即使外部类...

    c++单例模式线程日志类

    在这个特定的场景中,我们讨论的是一个实现了单例模式的日志类,该类专为多线程环境设计,具备日志等级控制、精确的时间戳以及可变长参数和标准格式化输出的功能。 首先,让我们深入了解单例模式。单例模式的主要...

    Oracle jdbc 单例 工具类

    - 静态内部类单例:利用类加载机制保证线程安全,避免了同步开销。 4. **工具类设计**: - `DB.java`:可能包含静态方法,如`getConnection()`,返回数据库连接。使用单例模式确保全局只存在一个`Connection`实例...

Global site tag (gtag.js) - Google Analytics