一、核心作用:
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
二、优点:
1、由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决;
2、单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
三、应用场景:
1、Windows的Task Manager(任务管理器)就是很典型的单例模式;
2、Windows的Recycle Bin(回收站)也是典型的单例应用,在整个系统运行过程中,回收站一直维护着仅有的一个实例;
3、项目中,读取配置文件的类,一般也只有一个对象,没有必要每次使用配置文件数据,每次new一个对象去读取;
4、网站的计数器,一般也是采用单例模式实现,否则难以同步;
5、应用程序的日志应用,一般都采用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加;
6、数据连接池的设计一般也是采用单例模式,因为数据连接是一种数据库资源;
7、操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统;
8、在servlet编程中,每个Servlet也是单例;
四、常见的五种单例模式实现方式:
主要:
-饿汉式(线程安全,调用效率高。但是,不能延时加载)
-懒汉式(线程安全,调用效率不高。但是,可以延时加载)
其它:
-双重检测式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
-静态内部类式(线程安全,调用效率高。但是,可以延时加载)
-枚举单例式(线程安全,调用效率高,不能延时加载)
五、代码(java实现):
/**
* 饿汉式单例模式
* 线程安全,调用效率高。但是,不能延时加载
* @author ly1
*
*/
public class Singleton1 {
private static Singleton1 instance = new Singleton1();
private Singleton1(){
}
public static Singleton1 getInstance(){
return instance;
}
}
/**
* 懒汉式单例模式
* 线程安全,调用效率不高。但是,可以延时加载
* @author ly1
*
*/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2(){
}
public static synchronized Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
}
/**
* 双重检测单例模式
* 由于JVM底层内部模型原因,偶尔会出问题。不建议使用
* @author ly1
*
*/
public class Singleton3 {
private static Singleton3 instance;
private Singleton3(){
}
public static Singleton3 getInstance(){
if(instance == null){
synchronized(Singleton3.class){
if(instance == null){
instance = new Singleton3();
}
}
}
return instance;
}
}
/**
* 静态内部类单例模式
* 线程安全,调用效率高,可以延时加载,结合了饿汉式与懒汉式的优点
* @author ly1
*
*/
public class Singleton4 {
private static class Holder{
private static final Singleton4 instance = new Singleton4();
}
public Singleton4 getInstance(){
return Holder.instance;
}
private Singleton4(){
}
}
/**
* 枚举实现单例模式
* 线程安全,调用效率高,不能延时加载。JVM底层保证单例。
* @author ly1
*
*/
public enum Singleton5 {
INSTANCE;
//其它操作
private void operation(){
}
}
/**
* 单例模式的测试,这里只测试第一个饿汉式单例模式
* @author ly1
*
*/
public class Client {
public static void main(String[] args) {
Singleton1 s1 = Singleton1.getInstance();
Singleton1 s2 = Singleton1.getInstance();
System.out.println(s1 == s2);
}
}
结果:true
六、五种单例模式调用效率的测试:
/**
* 测试五种单例模式的调用效率,这里测的是第一种,其它的类似
* @author ly1
*
*/
public class PerformanceTest {
public static void main(String[] args) throws Exception {
int threadNum = 10;
//记录开始时间
long start = System.currentTimeMillis();
//管理线程
final CountDownLatch count = new CountDownLatch(threadNum);
//开启十个线程,每个线程调用1亿次
for (int i = 0; i < threadNum; i++) {
new Thread(new Runnable(){
@Override
public void run() {
for (int j = 0; j < 100000000L; j++) {
Object obj = Singleton5.INSTANCE;
}
count.countDown();
}
}).start();
}
//保证main线程在这十个线程执行完后再执行
count.await();
//记录结束时间
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start) + "ms");
}
}
测试结果:
方式 | 效率 |
---|---|
饿汉式 | 100ms |
懒汉式 | 37183ms |
双重检测式 | 104ms |
静态内部类式 | 99ms |
枚举式 | 99ms |
分析:
1、可以看出懒汉式由于在方法上加了同步锁,导致每次调用都要同步,性能大大降低。
2、如果创建对象资源耗时,要求延时加载,建议选择静态内部类式;
3、如果不要求延时加载,建议选择枚举式。
七、其他相关问题
以上五种方式,除了枚举式,其他的都可以用反射和反序列化破解,造成不是单例。
-反射,虽然构造器私有化,依然还是可以调用。(参考)
-反序列化,先将对象通过对象流写入到文件中,再用对象流读取,会创建新的对象。(关于对象流)
当然,写普通的项目,不用考虑这些,这里只是提一下。