定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构 造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
单例设计模式,是软件开发中最常用的设计模式之一、。即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。比如代表JVM运行环境的Runtime类。
因为Singlton类封装它的唯一实例,这样它可以严格控制客户怎样访问以及何时访问它。 对唯一实例的受控访问。
单例类有状态,可以有子类来继承。
1)注意点
-
如何保证只能有一个实例
构造器私有化
-
必须自行创建这个实例(内部提供当前类的实例)
含有一个该类的静态变量保存这个唯一实例(实例必须静态化)
-
自行向整个系统提供这个实例
对外提供获取该实例对象的方式(提供公共的静态的方法,返回当前类的 对象)
- 直接暴漏 ; 用静态变量的get方法获取
2)实现的常见形式
1.饿汉式
直接创建对象,不存在线程安全问题
饿: 着急, 不管用不用, 先创建
1.1直接实例化饿汉式,简洁直观
- 注意构造器私有化,自行创建并且用静态变量保存,向外提供实例
package interview.singleton;
//饿汉式,直接创建实例对象,不管你是否需要对象都会创建
public class Singleton1 {
public static final Singleton1 INSTANCE=new Singleton1();
private Singleton1(){
}
}
获取这个对象
package interview.singleton;
public class TestDemp {
public static void main(String[] args) {
Singleton1 s=Singleton1.INSTANCE;
System.out.println(s);
}
}
1.2枚举式 最简洁
package interview.singleton;
//饿汉, 枚举式
/*
* 枚举类型: 表示该类型的对象是有限的几个,限定为一个,就成了单例
* */
public enum Singleton2 {
INSTANCE
}
获取对象
package interview.singleton;
public class TestDemp {
public static void main(String[] args) {
Singleton2 s=Singleton2.INSTANCE;
System.out.println(s);
}
}
但是这种方式打印的结果和第一种不一样。直接打印出对象的名字INSTANCE
1.3静态代码块的形式 适合复杂实例化
可能需要从配置文件中读一堆初始化的数据
package interview.singleton;
import java.io.IOException;
import java.util.Properties;
public class Singleton3 {
//自行创建,并且用静态变量保存
public static final Singleton3 INSTANCE;
private String info;
static {
try {
Properties properties=new Properties();
//因为将配置文件放在了src下,所以可以使用类加载器加载
properties.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));
INSTANCE= new Singleton3(properties.getProperty("info"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//构造器私有化
private Singleton3(String info){
this.info= info;
}
}
相应的配置文件 ./src/single.propertites
info=sosleepy
2.懒汉式
2.1线程不安全,适用于单线程
package interview.singleton;
/*
* 构造器私有化
* 用一个静态变量保存这个唯一的实例
* 提供一个静态方法,获取这个实例对象
*
* 懒汉式:延迟创建这个实例对象,
* */
public class Singleton4 {
private static Singleton4 instance;
private Singleton4(){
}
public static Singleton4 getInstance(){
if(instance==null){
instance=new Singleton4();
}
return instance;
}
}
测试
package interview.singleton;
public class TestDemp {
public static void main(String[] args) {
Singleton4 s1=Singleton4.getInstance();
Singleton4 s2=Singleton4.getInstance();
System.out.println(s1==s2);
}
}
会涉及到的线程安全问题:
package interview.singleton;
import java.util.concurrent.*;
public class TestDemp {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Singleton4 s1=Singleton4.getInstance();
// Singleton4 s2=Singleton4.getInstance();
// System.out.println(s1==s2);
Callable<Singleton4> c=new Callable<Singleton4>() {
@Override
public Singleton4 call() throws Exception {
return Singleton4.getInstance();
}
};
//创建线程池
ExecutorService es= Executors.newFixedThreadPool(2);
Future<Singleton4> f1=es.submit(c);
Future<Singleton4> f2=es.submit(c);
Singleton4 s1=f1.get();
Singleton4 s2=f2.get();
System.out.println(s1==s2);
es.shutdown();
}
}
输出为false
2.2线程安全(适用于多线程)
利用同步解决
package interview.singleton;
public class Singleton5 {
private static Singleton5 instance;
private Singleton5(){
}
public static Singleton5 getInstance(){
synchronized (Singleton5.class){ //当前类为锁对象
if(instance==null){
instance=new Singleton5();
}
return instance;
}
}
}
package interview.singleton;
import java.util.concurrent.*;
public class TestDemp {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Singleton4 s1=Singleton4.getInstance();
// Singleton4 s2=Singleton4.getInstance();
// System.out.println(s1==s2);
Callable<Singleton5> c=new Callable<Singleton5>() {
@Override
public Singleton5 call() throws Exception {
return Singleton5.getInstance();
}
};
//创建线程池
ExecutorService es= Executors.newFixedThreadPool(2);
Future<Singleton5> f1=es.submit(c);
Future<Singleton5> f2=es.submit(c);
Singleton5 s1=f1.get();
Singleton5 s2=f2.get();
System.out.println(s1==s2);
es.shutdown();
}
}
输出结果
true
Process finished with exit code 0
对于多线程, 后来的线程就没必要锁了, 加个if 判定提高性能
不用让线程每次都加锁,而是在实例未被创建的时候再枷锁处理,同时也保证多线程的安全。这种做法成为Double-Check Locking 双重锁定。
package interview.singleton;
public class Singleton5 {
private static Singleton5 instance;
private Singleton5(){
}
public static Singleton5 getInstance(){
if(instance==null){
synchronized (Singleton5.class){ //当前类为锁对象
if(instance==null){
instance=new Singleton5();
}
}
}
return instance;
}
}
2.3静态内部类形式,适用于多线程
package interview.singleton;
/*
*在内部类被加载和初始化时,才创建对象
*静态内部类不会自动随着外部类的加载和初始化而初始化,他是要单独去加载和初始化的。
* 因为是在内部类加载和初始化时,创建的,所以是线程安全的。
* */
public class SIngleton6 {
private SIngleton6(){
}
private static class Inner{
private static final SIngleton6 INSTANCE=new SIngleton6();
}
public static SIngleton6 getInstance(){
return Inner.INSTANCE;
}
}
总结: 饿汉式,枚举形式最简单; 懒汉式,静态内部类形式最简单。
单例模式优点
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的
产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可
以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方
式来解决。
应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志
文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。 - 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库
资源。 - 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置
文件数据,都生成一个对象去读取。 - Application 也是单例的典型应用
- Windows的Task Manager (任务管理器)就是很典型的单例模式
- Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程
中,回收站一直维护着仅有的一个实例。