东东东 陈煜东的博客

标签存档: Java

Java Singleton 单例模式

在设计模式中,单例模式(Singleton)是最长见得一种设计模式之一。什么是单例模式呢?就是在整个系统中,只有一个唯一存在的实例。这样的情况可以干什么用呢?比如可以统计网站的访问量,一些连接池(数据库连接池等)。

一个最简单的单例模式 – 饿汉模式

那么怎么能保证只有一个对象的存在呢?首先得有一个static的实例,这个方法保证了一个class只有一个实例。还得防止外界使用构造器来new一个实例。

//一个没有封装的单例模式
public class Singleton {
    public static final Singleton singleton = new Singleton();
    private Singleton(){}
}

外界就可以使用Singleton.singleton这样的方法来调用了。但是这样存在的问题就是分装不够好,添加一个方法,返回singleton的引用。如下

//最简单的封装单例改进版。饿汉模式
public class Singleton {
    public static final Singleton singleton = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){ return singleton; }
}

当代码写到这,我们终于可以松一口气了,原来单例模式也很简单呀,就这么几行的代码。对,其实这个是最简单的一种方式,能够应付大部分的场景的。

不过,其他class在引用Singleton而不使用的时候,虚拟机会自动加载这个类,并且实例化这个对象(这点知道Java虚拟机的类加载就会了解那么一些)。于是我们就有了下面的写法。

延迟实例化(懒汉模式) – 在调用时进行实例化

经常能看见其他的单例模式会教下面的代码,这样的人估计是从《设计模式 – 可复用面向对象软件的基础》那本书看来的。这本书使用的是C++语言写的,然后就将其转到了Java平台来。

首先他们会说我们的代码应该先这样,在调用的时候,发现为null,再进行实例化该类。

public class Singleton {
    public static final Singleton singleton = null;
    private Singleton(){}
    public static Singleton getInstance(){
        if(singleton == null){ //如果singleton为空,表明未实例化
           singleton = new Singleton();
        }
        return singleton;
    }
}

然后他们还会说,这样在多线程的情况下会出现这样的情况:两个进程都进入到if (singleton == null),于是两个线程都对这个进行实例化,这样就出问题啦。

所以他们又说应该使用synchronize关键字,在实例化之前,进行加锁行为。于是又产生了一下的代码

public class Singleton {
    public static final Singleton singleton = null;
    private Singleton(){}
    public static Singleton getInstance(){
        if(singleton == null){ //如果singleton为空,表明未实例化
           synchronize (Singleton.class){
               if( singleton == null ) { // double check 进来判断后再实例化。
                   singleton = new Singleton(); 
               } 
        }
        return singleton;
    }
}

他们就会说,他们的这个代码使用了double check (双重检测),以防止这样的情况:当两个线程执行完第一个singleton == null 后等待锁, 其中一个线程获得锁并进入synchronize后,实例化了,然后退出释放锁,另外一个线程获得锁,进入又想实例化,会判断是否进行实例化了,如果存在,就不进行实例化了。

他们会说,这样的代码perfect,很完美,既解决了多线程带来的问题,又解决了延迟实例化的方式。

我觉得这样的代码只是将C++版的单例模式复制到Java平台,没有Java的特色。第一个就是一个Java特色的代码,它解决了多线程的问题,因为JLS(Java Language Specification)中规定了一个类(Singleton.class)只会被初始化一次,但是不能解决延迟实例化的情况。如需要延迟实例化,可以看下面的方法,使用内部类来实现。

使用内部类的单例模式 (懒汉模式)

刚才说了,第一个不能解决延迟实例化Singleton对象的问题。所以我们使用内部类来进行,看看代码。

//一个延迟实例化的内部类的单例模式
public final class Singleton {
    
    //一个内部类的容器,调用getInstance时,JVM加载这个类
    private static final class SingletonHolder {
        static final Singleton singleton =  new Singleton();
    }

    private Singleton() {}

    public static Singleton getInstance() {
        return SingletonHolder.singleton;
    }
 }

我们来看看这个代码。首先,其他类在引用这个Singleton的类时,只是新建了一个引用,并没有开辟一个的堆空间存放(对象所在的内存空间)。接着,当使用Singleton.getInstance()方法后,Java虚拟机(JVM)会加载SingletonHolder.class(JLS规定每个class对象只能被初始化一次),并实例化一个Singleton对象。

这样做就可以解决前面说的对线程多次实例化对象延迟实例化对象的问题了。

缺点:不过你会使用这样复杂的方式嘛?代码那么多。只是为了延迟一个对象的实例化,引入另外一个class。就为了延迟那么一次对象延迟的实例化,延缓Java的heap堆内存。为此付出的代价是引入一个class,需要在Java的另外一个内存空间(Java PermGen 永久代内存,这块内存是虚拟机加载class文件存放的位置)占用一个大块的空间。

还存在的一些问题

好了,在许多情况其实用第一种方法就差不多可以了。在特殊情况下,还是存在着一些问题。

用反射生成对象

如果使用Java的反射机制来生成对象的话,那么单例模式就会被破坏。

//使用反射破坏单例模式
Class c = Class.forName(Singleton.class.getName());  
Constructor constructor = c.getDeclaredConstructor();  
constructor.setAccessible(true);  
Singleton singleton = (Singleton)ct.newInstance();  

对于用反射破坏单例模式的,是不对其进行代码保护的,即由此造成的后果,由写反射的构建单例实例的人负责。所以我们就不用担心反射带来的问题了。

分布式上,解决单例模式

对于分布式上的单例模式,应该使用RMI(Remote Method Invocation 远程方法调用)来进行。或者使用web serivce,在单个服务器上存在单例,其他的机器使用SOAP协议进行访问。

不同的ClassLoader(类加载器)加载Singleton

在不同的ClassLoader加载Singleton,他们是不一样的。就像在不同的package中相同的类名,他们是不同的类。同样的,在不同的ClassLoader上加载的类,他们尽管代码一样,还是属于不同的类。

这样,需要自己写classloader,保证Singleton.class的加载唯一。

参考文章:http://www.oschina.net/question/9709_102019

单例模式的对象是否会被JVM回收?

对于这个问题,在还没实例化单例的时候,对象不存在,单实例化后,那么singleton的引用就存在的,只要Singleton.class存在虚拟机中。那么什么时候Singleton.class会被回收呢?对于这个问题,牵扯了许多的问题。因为Singleton对象存在,所以Singleton.class就也存在,这样形成了相互依赖,所以不会被JVM垃圾回收。

网上有个文章验证了他的想法 http://blog.csdn.net/zhengzhb/article/details/7331354

使用反序列化生成对象

如果你的Singleton序列化了,那么通过反序列化方式可以生成一个对象。通过增加readResolve方法来解决。如下

//最简单的封装单例改进版。饿汉模式。序列化及反序列化解决
public class Singleton implements java.io.Serializable {
    public static final Singleton singleton = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){ return singleton; }
    private Object readResolve() {  return singleton;   }  
}

使用clone()克隆对象

Object的clone()方法默认是抛出CloneNotSupportedException异常的,所以只要不覆盖该方法,调用的时候,也会抛出异常。

effective java作者提到用enum枚举来实现单例模式。

扩展阅读:使用enum来进行Java的单例模式:http://coolxing.iteye.com/blog/1446648

分类: Java

解决Could not create the Java Virtual Machine

安装完openjdk64bit之后,运行java出现问题。

本篇记录tomcat的配置。请新建文件${TOMCAT}/bin/setenv.sh(或者Windows下:${TOMCAT}/bin/setenv.bat),tomcat会自动读取该配置。

OutOfMemoryError: Java heap space 堆空间不足

Error occurred during initialization of VM Could not reserve enough space for object heap Error: Could not create the Java Virtual Machine.

发现是虚拟机要使用的堆容量不足。

修改环境变量,调整堆内存大小

1.Linux修改环境变量

vi /etc/environment

2.增加此行

export _JAVA_OPTIONS="-Xms64m -Xmx64m -Xmn32m"

其中的64m表示使用64M的内存。-Xms是堆的最小大小,-Xmx是堆的最大值,-Xmn是堆中的新生代最小值。Xmn中的值不能超过总的堆内存大小

3.生效环境变量

source /etc/environment

4.运行java。看看能否运行,如果还是不能运行,到第二步,减小内存。

java -version

OutOfMemoryError: PermGen space 永久代保存区域溢出

PermGen是虚拟机中的永久代,不属于堆。里面主要有class的信息等。出现这个错误一般都是加载的class过多了。比如你的tomcat有许多的应用,每个应用的class文件或者jar包非常多。虽然Java在运行程序时,根据需要加载class文件,但是一旦加载后,移除该class的就会比较少了,但是java肯定有这个机制,只是这个机制的判断比较复杂,要如何判断一个class不再使用,确实比较麻烦。

要设置Java的PermGen,见下面的参数

JAVA_OPTIONS="-XX:PermSize=64M -XX:MaxPermSize=128m"

这个是设置初始的PermGen的大小为64M,最大的PermGen为128m。

分类: Java

java 64bit 32bit environment path install

在编写Java程序的时候,程序员需要安装JDK,JDK里面已经包含了JRE,JRE里包含了JVM虚拟机了。Eclipse的运行需要JRE的支持。所以我们需要JRE。 JDK有64bit和32bit之分,Eclipse也有64bit和32bit之分。

为什么是64bit?

现在内存越来越大,而32bit的系统只能寻址4G的空间,由于一些系统中断的地址占用,32bit的系统只能识别3G的内存。要想充分利用剩余的内存,只能换成64bit操作系统。

64bit有什么劣势

由于64bit的系统是最近发展起来的,有的软件只能运行于32bit上,在64bit环境中不是太好,所以软件的兼容性是需要解决的。许多人不愿升级到64bit操作系统就是这个原因。

为什么需要32bit的JDK

写一个项目需要使用JMF,而JMF是2002年左右出来的东西,当时还不支持64bit的系统。于是在我的64bit的JDK上不能运行,会报错误。无奈,只能从别人的32bit的JDK拷贝到我的电脑上运行。

更改环境之前需要做什么

删除一些文件。如果系统之前是使用exe安装方式安装的JDK,那么在C://windows/system32会有3个文件,是java.exe javaw.exe javaws.exe。为什么当时使用exe安装JDK的时候不要配置环境变量,就可以运行java程序?就是因为安装文件在这个目录下放了这几个文件。

获得32bit的JDK。可以下载一个32bit的JDK。

获得32bit的Eclipse。如果直接在32bit的jre上运行64bit的eclipse会出现问题的。会出现如下错误

Failed to load the JNI shared library "F:\jdk1.7.0_03\bin\..\jre\bin\client\jvm.dll".

如何更改环境变量

右键计算机-属性,高级系统设置。

检测是否成功

打开cmd,输入命令java -version

分类: Java

log4j.properties配置文件使用说明

log4j是一个用于Java的控制信息的输出类库,下载地址apache log4j

log4j.properities是log4j的配置文件,放在classpath的根目录下。在运行时,log4j会自动查找配置文件log4j.properities,读取配置。

log4j可以用来代替System.out.println();来控制输出,有一天,可能想让输出到一个文件中去,或者不显示。毕竟不是所有的运行情况都能在控制台上运行并输出。

根记录等级

log4j.rootLogger = debug,CONSOLE,FILE;

说明我的所有的Log将来输出的最低级别是debug,并且使用CONSOLE和FILE输出目的地。其中CONSOLE、FILE只是一个命名变量,在下方具体指定不同的配置。

日志记录器Logger有5个正常级别level,分别是 debug 调试程序时输出的信息 info 应用程序在运行过程的信息 warn 会出现潜在错误信息 error 指出虽然会发生错误,但还不会影响程序继续运行 fatal 指出致命错误,会会导致应用程序退出

另外还有两个特别的日志记录级别 all 最低等级,用于显示所有日志记录 off 最高等级,用于关闭所有日志记录

显示内容的多少,是all>debug>info

输出源Appender log4j提供5个appender输出源,它们是 org.apache.log4j.ConsoleAppender

分类: Java

Exception一定要被处理,不只是catch

写了个小程序,用多线程去抓取一堆文件,编号是连续的,于是我开了100个线程去抓取,运行的时候,报异常了,貌似是栈溢出了。

后来改成20,再去抓取,这次就行了看见一个个那边下载,但是有时候能看见一些异常,这个异常是IO异常,具体不知道原因,因为下载许多个文件才出现一次。

反思

直接在catch中写e.printStackTrace()太糟糕了。除非是简单的一个小调试,测试代码是否能工作。如果真的在正式场合,或者该该异常后面的代码执行会对你的需求产生影响,就需要将其处理了,并且写入到文件中。这样以后才能检查到哪里出了问题。

这样多线程下载文件,不知道哪个没有下载下来,所以以后在处理异常的时候,应该把该异常处理的那个文件编号保存到文件里,这样在下载后就能查看哪个文件没下载完了。

把代码贴出来,这个是我用多线程下载连续文件的方法,并把它分成了好几个部分,每个部分下载自己的那部分,不会妨碍到其他的线程。

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.imageio.ImageIO;

public class SaveFile {
    private int intervalLength = 1000;//每个线程处理的区间长度

    private long finalId = 152000;//最后一个文件的id

    private static ExecutorService pool = Executors.newFixedThreadPool(20);

    private Integer lock=0;//区间的索引值,锁,用于多线程之前的赋值

    /**
     * 下载第i个
     * 
     * @param i
     */
    public void download(long i) {
        //TODO 写自己的下载代码吧
    }

    public void start() {
        System.out.println("has segment:"+finalId/intervalLength);

        for (lock = 0; lock <= finalId/intervalLength; lock++) {

            synchronized (lock) {
                //把当前的区间赋值给临时变量,如果直接用lock,
                //那么多线程里的数值就变成了最后一个了,速度太快了。
                final long a = lock;
                pool.execute(new Runnable() {
                    @Override
                    public void run() {
                        for (long i = a*intervalLength; i < (a+1)*intervalLength; i++) { 

                            download(i);
                        }
                    }
                });
            }

        }
    }

    public static void close(){
        pool.shutdown();
    }

    public static void main(String[] args) {
        SaveFile s=new SaveFile();
        s.start();
        SaveFile.close();
    }
}

分类: Java

CentOS yum安装sun Java jre jdk和openjdk

在Linux下的tty终端下,安装sun jre,但是去Oracle安装,下载居然还要登录,使用cookie,没办法。直接到http://www.java.com/下载了。

说明:如果使用yum install 安装软件,具体的软件名称不知道,tab又不管用,可以使用yum list j* 来查看相关的软件名称

安装sun JDK

进入http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html,寻找最新的JDK地址,在以下代码中url替换最新的jdk地址。

没办法直接通过wget下载,需要cookie。(感谢网友评论留下的方法。)

wget --no-check-certificate --no-cookies --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com" "http://download.oracle.com/otn-pub/java/jdk/7u15-b03/jdk-7u15-linux-x64.rpm"

改名后,使用一下代码安装

yum install jdk-7u15-linux-x64.rpm

安装sun jre

到http://java.com/zh_CN/download/manual.jsp找到自己的版本下载,我的是下载Linux x64 RPM,地址http://javadl.sun.com/webapps/download/AutoDL?BundleId=67387

下载jre,并重命名

wget -O jre-7u6-linux-x64.rpm http://javadl.sun.com/webapps/download/AutoDL?BundleId=67387

安装

yum install jre-7u6-linux-x64.rpm

安装成功

[root@317304 ddd]# java -version
java version "1.7.0_06"
Java(TM) SE Runtime Environment (build 1.7.0_06-b24)
Java HotSpot(TM) 64-Bit Server VM (build 23.2-b09, mixed mode)

安装openjdk

yum install java-1.7.0-openjdk

参考:http://openjdk.java.net/install/

分类: Java

unicode与字符相互转换

在许多场合中,我们会使用到Unicode码,但是unicode编码的形式我们却没办法直观知道是哪个字符,这样我们就需要对讲Unicode码转换成我们认识的汉字字符。

什么是Unicode?

Unicode(统一码、万国码、单一码、标准万国码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简化地方式来呈现和处理文字。

–Wikipedia

Unicode字符用什么表示?

Unicode通常使用U+XXXX,XXXX四位十六进制数,例如U+4F60。但是在Unicode 3.0后,Unicode使用“U-”然后紧接着八位十六进制数,在Unicode 3.0之前,使用的”U+”后面跟的四位十六位进制数。

在Java对Unicode的实现就不是用这样表示的。Java的形式是”\uXXXX”,例如”\u4F60” 是汉字“你”。

有时候,我们使用在文件中使用汉字字符,但是当文件的编码发生改变的时候,那么就会出现乱码了。如果我们使用unicode的转移形式(”\uXXXX”)去表示中文,那么我们改变文件的编码的时候,就不会出现乱码了,因为我们使用的是ASCII字面值去存储文本的。

但是这样观看Unicode码不够直观,我们不知道具体是什么汉字,所以我们需要通过转换,这样我们就知道具体对应的是什么汉字了。

将Unicode码转化为汉字字符

public static String UnicodetoString(String s){
    String ss[] =  s.split("\\\\u");
    StringBuilder sb=new StringBuilder(ss.length-1);
    for (int i = 1; i < ss.length; i++) {
        sb.append((char)Integer.parseInt(ss[i], 16));
    }
    return sb.toString();
}

获取汉字字符的Unicode码

public static String StirngtoUnicode(String s){
    StringBuilder sb=new StringBuilder();
    for (int i = 0; i < s.length(); i++) {
        sb.append(String.format("\\u%04x", (int)s.charAt(i)));
    }
    return sb.toString();
}

 

因为String.format()方法是用return new Formatter().format(format, args).toString(); 所以先创建一个Formatter对象,减少对象的使用。

public static String StringtoUnicode(String s){

    Formatter formatter = new Formatter();
    for (int i = 0; i < s.length(); i++) {
        formatter.format("\\u%04x", (int)s.charAt(i));
    }
    return formatter.toString();
}

http://docs.oracle.com/javase/tutorial/i18n/text/string.html

分类: Java

ISafeRunnable 类定义未找到

在开发swt应用中,使用了TableViewer类,导入了org.eclipse.jface_3.7.0.v20110928-1505.jar类库了,eclipse也不报错了,但是运行是就是报以下错误。

Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/core/runtime/ISafeRunnable
at gui.Table.(ZZZTable.java:23)
at gui.Table.main(ZZZTable.java:91)
Caused by: java.lang.ClassNotFoundException: org.eclipse.core.runtime.ISafeRunnable

根据给出的提示说org/eclipse/core/runtime/ISafeRunnable未找到,于是就引入了org.eclipse.core.runtime_3.7.0.v20110110.jar包,运行时还是报这个错误。

后来得知,这个错误是由于给类的定义是在org.eclipse.equinox.common_3.6.0.jar,这样运行是不报错了。在http://www.eclipsezone.com/eclipse/forums/m92035942.html说到

The ISafeRunnable interface is located in the equinox plugin (org.eclipse.equinox.common). If you add the SWT library to your project, it should be added to the build path automatically. You can do this by right-clicking your project, choosing Build Path/Add Libraries… and then selecting the SWT Library. Afterwards, your project should contain a new folder, having the same icon as the JRE folder, and containing a couple of jars, including org.eclipse.swt… and org.eclipse.equinox.common.

查看了下ISaftRunnable该类的定义说明

Safe runnables represent blocks of code and associated exception handlers. They are typically used when a plug-in needs to call some untrusted code (e.g., code contributed by another plug-in via an extension). This interface can be used without OSGi running. Clients may implement this interface.

不过我在导入这个类库是在eclipse rpc版中导入的,不知道其他的eclipse有没有这个类库。

分类: Java

Copyright © 2017 东东东 陈煜东的博客 粤ICP备13059639号-1

SITEMAP回到顶部 ↑