欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java 单例模式

程序员文章站 2022-07-13 23:46:42
...

单例模式是设计模式中经常用到的一种,我最近看了下,再此记录下自己的学习成果,以供大家参考借鉴。

1. 定义:

      确保一个类只有一个实例,并提供一个全局访问点。

2. 设计思路剖析

       我们如果把某个类设计成自己管理的一个单独实例,同时也避免其他类再自行实例产生实例,所以要把这个类的构造方法声明为私有,只能通过这个类实例化,想要获取这个类的实例,只能通过单间类,我们需要对全局提供这个实例的访问点,当你像类查询时,返回单个实例。

3. 经典单例模式实现

public class Singleton {
    //利用静态变量来记录Singleton类的唯一实例
    private static Singleton uniqueInstance;
    //把构造器声明为私有,确保只有自身Singleton类内才可以调用构造器。
    private Singleton() {
    }
    //用getInstance()方法实例化对象,并返回这个实例
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
	    //当需要Singleton实例时,uniqueInstance为空,我们则利用私有的构造器,产生一个Singleton实例,并赋值到uniqueInstance静态变量中。
            //当我们不需要时,它就永远不会产生,这就是"延迟实例化"
        }
        return uniqueInstance;
    }
    
}	

 

4. 使用场景

    ①. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~

    ②. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

    ③. 网站的计数器,一般也是采用单例模式实现,否则难以同步。

    ④. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

    ⑤. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。

    ⑥. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。

    ⑦. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

    ⑧. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。

    ⑨. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.   

    ⑩. 结巴分词的WordDictionary类

public class WordDictionary {
    private static WordDictionary singleton;
    private static final String MAIN_DICT = "/dict.txt";
    private static final String USER_DICT = "/userDict.txt";
    private static final String USER_STOP_DICT = "/userStopDict.txt";
    private static String USER_DICT_SUFFIX = ".dict";
    //停用词频次数组
    private static Map<String, Double> stopWordFreqs = new HashMap<String, Double>();
    //2018/10/25 频次数组
    public final Map<String, Double> freqs = new HashMap<String, Double>();
    public final Set<String> loadedPath = new HashSet<String>();
    private Double minFreq = Double.MAX_VALUE;
    private Double total = 0.0;
    private DictSegment _dict;


    private WordDictionary() {
        this.loadDict();
        this.loadUserDict();
        this.loadUserStopDict();
    }


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


    /**
     * for ES to initialize the user dictionary.
     *
     * @param configFile
     */
    public void init(Path configFile) {
        String abspath = configFile.toAbsolutePath().toString();
        System.out.println("initialize user dictionary:" + abspath);
        synchronized (WordDictionary.class) {
            if (loadedPath.contains(abspath))
                return;

            DirectoryStream<Path> stream;
            try {
                stream = Files.newDirectoryStream(configFile, String.format(Locale.getDefault(), "*%s", USER_DICT_SUFFIX));
                for (Path path : stream) {
                    System.err.println(String.format(Locale.getDefault(), "loading dict %s", path.toString()));
                    singleton.loadUserDict(path);
                }
                loadedPath.add(abspath);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                // e.printStackTrace();
                System.err.println(String.format(Locale.getDefault(), "%s: load user dict failure!", configFile.toString()));
            }
        }
    }


    /**
     * let user just use their own dict instead of the default dict
     */
    public void resetDict() {
        _dict = new DictSegment((char) 0);
        freqs.clear();
    }

    public void loadUserStopDict() {
        InputStream is = this.getClass().getResourceAsStream(USER_STOP_DICT);
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
            long s = System.currentTimeMillis();
            while (br.ready()) {
                String line = br.readLine();
                String[] tokens = line.split("[\t ]+");
                // 2018/10/25  每行参数小于两个为不正常。
                // 词 \t  频率 \t  词性
                if (tokens.length < 2)
                    continue;
                String word = tokens[0];
                double freq = Double.valueOf(tokens[1]);
                total += freq;
                stopWordFreqs.put(word, freq);
            }
            System.out.println(String.format(Locale.getDefault(), "user stop dict load finished, time elapsed %d ms",
                    System.currentTimeMillis() - s));
        } catch (IOException e) {
            System.err.println(String.format(Locale.getDefault(), "%s load failure!", USER_STOP_DICT));
        } finally {
            try {
                if (null != is)
                    is.close();
            } catch (IOException e) {
                System.err.println(String.format(Locale.getDefault(), "%s close failure!", USER_STOP_DICT));
            }
        }
    }

    public void loadUserDict() {
        InputStream is = this.getClass().getResourceAsStream(USER_DICT);
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
            long s = System.currentTimeMillis();
            while (br.ready()) {
                String line = br.readLine();
                String[] tokens = line.split("[\t ]+");
                // TODO: 2018/10/25  每行参数小于两个为不正常。
                // TODO: 词 \t  频率 \t  词性
                if (tokens.length < 2)
                    continue;

                String word = tokens[0];
                double freq = Double.valueOf(tokens[1]);
                total += freq;
                // TODO: 2018/10/25 添加词语到前缀树里面,统计词频
                word = addWord(word);
                freqs.put(word, freq);
            }
            // normalize
            for (Entry<String, Double> entry : freqs.entrySet()) {
                entry.setValue((Math.log(entry.getValue() / total)));
                minFreq = Math.min(entry.getValue(), minFreq);
            }
            System.out.println(String.format(Locale.getDefault(), "main dict load finished, time elapsed %d ms",
                    System.currentTimeMillis() - s));
        } catch (IOException e) {
            System.err.println(String.format(Locale.getDefault(), "%s load failure!", USER_DICT));
        } finally {
            try {
                if (null != is)
                    is.close();
            } catch (IOException e) {
                System.err.println(String.format(Locale.getDefault(), "%s close failure!", USER_DICT));
            }
        }
    }


    public void loadDict() {
        _dict = new DictSegment((char) 0);
        InputStream is = this.getClass().getResourceAsStream(MAIN_DICT);
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
            long s = System.currentTimeMillis();
            while (br.ready()) {
                String line = br.readLine();
                String[] tokens = line.split("[\t ]+");
                // TODO: 2018/10/25  每行参数小于两个为不正常。
                // TODO: 词 \t  频率 \t  词性(可省略)
                if (tokens.length < 2)
                    continue;

                String word = tokens[0];
                double freq = Double.valueOf(tokens[1]);
                total += freq;
                // TODO: 2018/10/25 添加词语到前缀树里面,统计词频
                word = addWord(word);
                freqs.put(word, freq);
            }
            // normalize
            for (Entry<String, Double> entry : freqs.entrySet()) {
                entry.setValue((Math.log(entry.getValue() / total)));
                minFreq = Math.min(entry.getValue(), minFreq);
            }
            System.out.println(String.format(Locale.getDefault(), "main dict load finished, time elapsed %d ms",
                    System.currentTimeMillis() - s));
        } catch (IOException e) {
            System.err.println(String.format(Locale.getDefault(), "%s load failure!", MAIN_DICT));
        } finally {
            try {
                if (null != is)
                    is.close();
            } catch (IOException e) {
                System.err.println(String.format(Locale.getDefault(), "%s close failure!", MAIN_DICT));
            }
        }
    }


    private String addWord(String word) {
        if (null != word && !"".equals(word.trim())) {
            String key = word.trim().toLowerCase(Locale.getDefault());
            // TODO: 2018/10/25 填充词典
            _dict.fillSegment(key.toCharArray());
            return key;
        } else
            return null;
    }


    public void loadUserDict(Path userDict) {
        loadUserDict(userDict, StandardCharsets.UTF_8);
    }


    public void loadUserDict(Path userDict, Charset charset) {
        try {
            BufferedReader br = Files.newBufferedReader(userDict, charset);
            long s = System.currentTimeMillis();
            int count = 0;
            while (br.ready()) {
                String line = br.readLine();
                String[] tokens = line.split("[\t ]+");

                if (tokens.length < 1) {
                    // Ignore empty line
                    continue;
                }

                String word = tokens[0];

                double freq = 3.0d;
                if (tokens.length == 2)
                    freq = Double.valueOf(tokens[1]);
                word = addWord(word);
                freqs.put(word, Math.log(freq / total));
                count++;
            }
            System.out.println(String.format(Locale.getDefault(), "user dict %s load finished, tot words:%d, time elapsed:%dms", userDict.toString(), count, System.currentTimeMillis() - s));
            br.close();
        } catch (IOException e) {
            System.err.println(String.format(Locale.getDefault(), "%s: load user dict failure!", userDict.toString()));
        }
    }


    public DictSegment getTrie() {
        return this._dict;
    }


    public boolean containsWord(String word) {
        return freqs.containsKey(word);
    }

    public boolean containsStopWord(String word) {
        return stopWordFreqs.containsKey(word);
    }

    public Double getFreq(String key) {
        if (containsWord(key))
            return freqs.get(key);
        else
            return minFreq;
    }
}

5. 多线程下的问题

        因为getInstance()方法不是同步方法,所以,会出现两次获取单例类实例,返回两个不同的对象。

解决方案1:

加上synchronized关键词。变成同步方法,这样就可以保证单例类的实例唯一化了。但是这样会造成额外负担,如果能接受,就不用多想了。如果getInstance()使用在频繁运行的地方,就考虑下下面的方案吧。

public class Singleton {
	//利用静态变量来记录Singleton类的唯一实例
    private static Singleton uniqueInstance;
	//把构造器声明为私有,确保只有自身Singleton类内才可以调用构造器。
    private Singleton() {
    }
	//用getInstance()方法实例化对象,并返回这个实例
    public static synchronized Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
			//当需要Singleton实例时,uniqueInstance为空,我们则利用私有的构造器,产生一个Singleton实例,并赋值到uniqueInstance静态变量中。
			//当我们不需要时,它就永远不会产生,这就是"延迟实例化"
        }
        return uniqueInstance;
    }
    
}	

解决方案2:

"直接"创建实例,而非延迟实例化的做法.如果应用程序总是创建并使用单例实例,或者在创建,运行时方面的负担不大,就可以使用下面的代码,"直接"创建此单例。

public class Singleton {	
		private static Singleton uniqueInstance = new Singleton();
		//把构造器声明为私有,确保只有自身Singleton类内才可以调用构造器。
		private Singleton() {
		}
		//用getInstance()方法实例化对象,并返回这个实例
		public static Singleton getInstance() {
			return uniqueInstance;
		}
	}	

解决方案3:

双重检查加锁,在getInstance()中减少使用同步。

利用双重检查加锁,首先检查实例是否已经创建,如果为创建,才进行同步。这样只有第一次会同步。

public class Singleton {
    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

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

6. 引用

应用场景部分原文来源

Head First 设计模式

 

 

 

上一篇: java单例模式

下一篇: Java 单例模式