实现一个单例对象的方法分三步:
<1>私有化本类的构造方法
<2>创建本类对象
<3>提供对外公共的访问方法,可以让外类访获取到该单例对象(也就是提供一个 get 方法)
例如:
package Singleton;
public class Singleton_test {
public static void main(String[] args) {
//这里可以获取该单例对象
Singleton.getInstance();
}
}
//饿汉式的单例模式,上来就把实例对象创建出来
class Singleton{
//私有化构造方法,可以保证在类外该类不能被实例化
private Singleton(){}
//创建该类的对象,并且用 private 修饰,保证外类不能对该对象赋值
//创建单例的目的就是确报下面创建的对象不被更改
private static Singleton s = new Singleton();
//提供静态的 get 方法,保证外类直接类名.方法获取该对象
public static Singleton getInstance(){
return s;
}
}
//懒汉式的单例模式
//只有在使用的时候才创建该类实例
class Singleton {
private Singleton(){};
private static Singleton s;
public static Singleton getInstance(){
if(s==null)//判断s是否为空,如果为空,创建该类实例,否则直接返回s
s=new Singleton();
return s;
}
}
对于饿汉式还有一种创建方法:上面那种是用的比较多的也是比较官方的创建单例模式的饿汉式方法,由于我们创建单例模式的目的是该实例对象不被更改,所以我们在前面加上一个 final 关键字修饰一下也可以,这三种方式都需要首先私有化该类的构造方法
//饿汉式的"民间"创建方法
class Singleton{
private Singleton(){}
public final static Singleton s = new Singleton();
}
饿汉式和懒汉式的优缺点:
在单线程的情况下:
饿汉式是用空间换时间,即:饿汉式上来就创建该单例对象,用的时候直接拿来用,节省了创建对象的时间,但是浪费了内存空间资源
懒汉式是用时间换空间,即:懒汉式在用到该单例对象的时候才创建,节省了内存空间,但是在时间上有延迟,因此懒汉式也叫延迟加载
在多线程情况下:
饿汉式是线程安全的
懒汉式有时候会存在安全隐患,比如有两个线程在调用该单例对象,线程1在执行到 if 语句的判断条件后(还没执行 new Singleton()这个语句),被其他线程抢走了执行权(比如 yield,priority 等限制的时候),恰好在此时,线程2进来了,此时的 s 仍然满足为 null 的条件,所以创建了一个实例对象,但是线程1在等到其他线程执行完之后,又继续执行实例对象的创建,导致创建了两个实例,解决方法是加锁或者同步:
解决方案:在getSingle()方法前增加synchronized进行同步
另一个问题,当加了synchronized后,那么很多线程来访问时,都要判断一下锁是哪个,这就造成速率下降,解决方案如下:
public staitc Single getSingle(){
if(s == null){
synchronized(Single.class){ -->B线程,等着A解锁才让进去
if(s == null){
-->A线程
s = new Single();
}
}
return s;
}
}
思路:
当s == null时,A线程进来了,他加了一下锁后进入第二个if(s ==null){},然后沉睡;此时,B也通过了第一个if(s == null),当B玩下执行时,遇到了synchronized(),发现这里A进行了加锁,没办法B线程只能等着,等A把锁解了。此时,A线程醒来了,它new 了一个对象后,继续玩下执行,然后把锁解了,这是s不等于null了。B发现A解锁了,它继续往下执行,发现s不等于null了,那它直接返回了A创建的那个对象s。当c线程访问getSingle方法时,只需判断s是否为null,而不用去判断锁对象了。因为s不等于null了,所以直接返回对象,这样就提高了效率
这里第一个 if(s==null) 的判断是为在已经创建了该类的实例的情况下,如果有其他线程来访问,则通过第一个 if 条件就可以判断 if 条件不成立,直接得到单例对象,如果不加第一个 if 条件,直接用 synchronized 的话,那么线程每次来的时候都需要判断是否有锁,如果有一个线程在这里加了锁,而且该实例已经存在,那么后面的线程再在这里等锁就是没必要的了
在计算机硬件条件越来越发达的今天,浪费点内存已经不算什么了,但是时间是宝贵的,时间是不会增加的,所以我们一般选择“饿汉式”的方法设计单例模式
最后登记式:
直接上示例代码:
父类:
//采用Map配置多个单例
public class MySingleton3 {
// 设立静态变量,直接创建实例
private static Map<String, MySingleton3> map = new HashMap<String, MySingleton3>();
// -----受保护的-----构造函数,不能是私有的,但是这样子类可以直接访问构造方法了
//解决方式是把你的单例类放到一个外在的包中,以便在其它包中的类(包括缺省的包)无法实例化一个单例类。
protected MySingleton3() {
System.out.println("-->私有化构造函数被调用,创建实例中");
}
// 开放一个公有方法,判断是否已经存在实例,有返回,没有新建一个在返回
public static MySingleton3 getInstance(String name) {
if (name == null) {
name = MySingleton3.class.getName();
System.out.println("-->name不存在,name赋值等于"+MySingleton3.class.getName());
}
if (map.get(name) == null) {
try {
System.out.println("-->name对应的值不存在,开始创建");
map.put(name, (MySingleton3)Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}else {
System.out.println("-->name对应的值存在");
}
System.out.println("-->返回name对应的值");
return map.get(name);
}
public Map<String, MySingleton3> getMap(){
return map;
}
}
子类1:
public class MySingleton3Childa extends MySingleton3 {
public static MySingleton3Childa getInstance() {
return (MySingleton3Childa) MySingleton3Childa
.getInstance("com.xq.mysingleton.MySingleton3Childa");
}
//随便写一个测试的方法
public String about() {
return "---->我是MySingleton3的第一个子类MySingleton3Childa";
}
}
子类2:
public class MySingleton3Childb extends MySingleton3 {
static public MySingleton3Childb getInstance() {
return (MySingleton3Childb) MySingleton3Childb
.getInstance("com.xq.mysingleton.MySingleton3Childb");
}
//随便写一个测试的方法
public String about() {
return "---->我是MySingleton3的第二个子类MySingleton3Childb";
}
}
其思想是为某个指定的类创建一个单例,这个指定的类就是 Map 中的 key,而这个单例对象就是 Map 中的 value
登记式单例模式最典型的应用就是Spring 的 IOC ,Ioc的原理是Java反射+登记式单例模式,反射就不用说了,利用 Spring.xml配置中的 Class 通过反射获取该类的一个实例,放进spring容器中,下面说一下登记式单例模式的实现:
比如在类 A 需要用到类 M 中的方法,需要在类 A 中注入类 M 的对象:
class A{
@AutoWired
M m1;
//...
}
那么在Spring ioc 的底层,会把类 A 作为 Map 的一个key,m1作为value,此时如果同一个包内的类 B 同样需要类 M 的方法,那么同样 Spring 会把类 B 作为 Map 的一个key,m2作为value,换句话说在 Spring 容器中每一个 M 的实例对应一个调用者,比如类 A 或者类 B