多线程下的单例模式详解
Posted 若曦`
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程下的单例模式详解相关的知识,希望对你有一定的参考价值。
1. 单例模式
(1) 单例模式简介
单例模式的作用
单例模式是为了一个类的示例只有一个,并且可以自己实例化,从而向系统提供该实例化的对象
单例模式的使用场景
-
整个程序的运行中只允许有一个类的实例
-
需要频繁实例化然后销毁的对象
-
创建对象时耗时过多或者耗资源过多,但又经常用到的对象
一般是对于那些业务逻辑上限定不能存在多实例的情况
例如:序列号生成器(解决不能id自增的问题),计数器—统计网站访问人数等场景,单例线程池等,都需要使用一个系统唯一实例来进行记录,若多实例计数则会不准确
单例模式的优缺点
优点:只有一个实例,节约了内存资源,提高了系统性能
缺点
- 没有抽象层,不能扩展
- 职责过重,违背了单一性原则
(2) 实现方式
① 饿汉式
饿汉式是指在第一次加载类的时候,就实例化对象,也就是在单例类的内部将类实例化
/**
* @author ruoxi
*/
public class TestSingleTon
public static void main(String[] args)
SingleTon singleTon1 = SingleTon.getSingleTon();
SingleTon singleTon2 = SingleTon.getSingleTon();
System.out.println(singleTon1==singleTon2); //true
/**
* 饿汉式
*/
class SingleTon
/**
* 注意需要使用 static和final修饰 并在这里直接实例化
*/
private static final SingleTon singleTon = new SingleTon();
/**
* 定义private私有构造器,表示只在类内部使用,亦指单例的实例只能在单例类内部创建
*/
private SingleTon()
/**
* 返回内部的singleTon实例
* @return
*/
public static SingleTon getSingleTon()
return singleTon;
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve()
return this.singleTon;
② 懒汉式
懒汉式就是不在类加载时就创建类的单例,而是在第一次使用实例的时候再创建
/**
* @author ruoxi
*/
public class TestSingleTon
public static void main(String[] args)
SingleTon2 singleTon21 = SingleTon2.getSingleTon();
SingleTon2 singleTon22 = SingleTon2.getSingleTon();
System.out.println(singleTon21==singleTon22); //true
/**
* 懒汉式
*/
class SingleTon2
/**
* 懒汉式不在此处实例化
*/
private static SingleTon2 singleTon=null;
private SingleTon2()
/**
* 如果singleTon为空则进行实例化
* @return
*/
public static SingleTon2 getSingleTon()
if(singleTon==null)
singleTon = new SingleTon2();
return singleTon;
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve()
return this.singleTon;
2. 多线程下的单例模式
对于饿汉式的实现方式,在多线程下也能保证单一实例
但是对于懒汉式来说,在一个线程获取实例的时候,可能会有另一个线程也在获取实例,导致产生两个及以上的实例对象出现
(1) Synchronized
/**
* synchronized实现多线程的单例模式
*/
class ThreadSingleTon
private static ThreadSingleTon singleTon = null;
private ThreadSingleTon()
/**
* 使用synchronized防止多个线程同时调用这个方式去创建
* @return
*/
public static synchronized ThreadSingleTon getThreadSingleTon()
//为空则创建对象
if(singleTon==null)
singleTon = new ThreadSingleTon();
return singleTon;
使用synchronized修饰方法,可以实现多线程下的单例模式
但是每次调用该方法,都会给方法加锁,而只有第一次创建对象的时候需要加锁,其他时候都不需要,这样会导致程序的效率地下,那么可以使用下面的方式(双重检查锁)
(2) 双重检查锁
/**
* 双重检查锁
*/
class ThreadSingleTon2
private static ThreadSingleTon2 singleTon = null;
private ThreadSingleTon2()
/**
* 双重检查锁 具体解释看代码注释
* @return
*/
public static ThreadSingleTon2 getThreadSingleTon()
if(getThreadSingleTon()==null)
//如果对象为空,则是第一次实例化,这时锁住对象
//给ThreadSingleTon.class加锁也可以
synchronized (singleTon)
//第二次判断是否为空,防止多线程操作时,在执行第一次判断后另一个线程完成了实例化
if(singleTon==null)
singleTon = new ThreadSingleTon2();
return singleTon;
使用上述方式,但还是会出现意外情况,这和java的线程工作内存有关,我们用下图流程来说明
也就是当线程1实例化后,还未放入主内存的间隙中,线程2拿到锁开始执行,又创建一个实例化对象
这时可以使用valotile使主内存中的对象对线程可见,来解决上述问题
(3) 双重检查锁+Volatile
这时就是完美的多线程下的单例模式了,解决了所有可能出现的问题
/**
* 双重检查锁+volatile
*/
class ThreadSingleTon2
/**
* 使用volatile使主内存中的singleTon对线程可见
*/
private volatile static ThreadSingleTon2 singleTon = null;
private ThreadSingleTon2()
/**
* 双重检查锁 具体解释看代码注释
* @return
*/
public static ThreadSingleTon2 getThreadSingleTon()
if(getThreadSingleTon()==null)
//如果对象为空,则是第一次实例化,这时锁住对象
//给ThreadSingleTon.class加锁也可以
synchronized (singleTon)
//第二次判断是否为空,防止多线程操作时,在执行第一次判断后另一个线程完成了实例化
if(singleTon==null)
singleTon = new ThreadSingleTon2();
return singleTon;
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve()
return this.singleTon;
补充知识点
为什么单例模式的内部类需要使用private static final修饰?
- private :不能让外部能直接操作该类属性;
- static :确保内部类是属于该类的,而不是类的实例化对象的,否则一个实例化对象就会有一个内部类,也就不是单例了;
- final :一方面防止指令重排出现问题,还有就是防止使用反射去操作内部类去生成新的对象,以确保单例模式的安全性;
以上是关于多线程下的单例模式详解的主要内容,如果未能解决你的问题,请参考以下文章