单例模式几乎是所有模式中最简单的,用的也挺多,今天就讲讲单例模式
单例模式的定义:
保证一个类仅有一个实例(在内存中仅有该类一份字节码),并提供一个访问它的全局访问方法即可,.实例就是说运行期间才有的
使用场景:比如一个类中的方法在很多类中都要使用到,有静态和非静态的方法,非静态的方法必须使用对象才能调用,
那就意味着在运行期间就会new很多类在内存中,这样会导致内存吃紧,因此需要优化,优化就是减少内存的空间,而内存是存储对象的,所以减少对象的个数就是优化了内存,因此只需要在内存中保存一份这个类的对象即可,
既然在外部类中不能new这个对象,那么这个类的构造函数一定要私有,这个单例类只能在本类中创建了,那么它必须要对外提供访问到这个对象的方法,(也就是说单例类要对外提供方法访问到这个对象的实例)所以单例的2个条件就出来了
1:构造函数要私有化,也就是创建对象权限只能在本类中使用
2:提供方法让外界能够拿到单例类的对象实例
下面就按照上面分析的写一个单例的demo
<span style="font-size:24px;">package cn.handcool.singlton;
public class SingleTon {
private SingleTon(){}
public static SingleTon getInstance(){
return null;
}
}</span>
上面的代码 getInstance()方法返回的是null对象,这肯定是不对的,必须要返回SingleTon类的实例对象,那么创建对象可以在一开始就创建,还有一种就是在运行期间创建,这就是所谓单例模式的懒汉式和饿汉式
首先讲讲饿汉式,因为这个最简单:
package cn.handcool.singlton;
public class SingleTon {
private static SingleTon instance = new SingleTon();
private SingleTon(){}
public static SingleTon getInstance(){
return instance;
}
}
懒汉式:
package cn.handcool.singlton;
public class SingleTon {
private static SingleTon instance ;
private SingleTon(){}
public static SingleTon getInstance(){
if(null==instance){
instance = new SingleTon();
}
return instance;
}
}
在java中有延迟加载的概念,懒汉式就是使用了延迟加载的思想
通俗点说,就是一开始不要加载资源或者手机,一直等到马上就要使用这个资源或者数据了,才去加载
所以也称lazy load,这个在实际开发中是一种很常见的思想,尽可能的节约资源
而懒汉式的延迟加载提现在那呢?
if(null==instance){
instance = new SingleTon();
}
上面的代码是只有去调用getInstance()方法才去创建SingleTon对象实例,如果不调用就不会创建对象,相对饿汉式就是一种延迟加载了,因为它不像饿汉式那样,一上来就创建对象缓存在内存中
懒汉式提现了java中的缓存思想,这在java中也有很多,比如android中加载图片,就是使用了缓存的思想,可以缓存在内存,或者sd卡以及其他存储空间,
缓存的思想:
简单的来讲,就是某些资源或者数据被频繁的使用,就不必每次访问网络去获取这些数据,第一效率会低,第二:节省了用户的流量,那我们就可以把这些数据缓存到内存中,每次操作到内存中 去找,内存中没有,就走网络去获取,第一次从网络获取后要设置到内存中,方便下次使用,缓存是一种典型的空间换时间的方案.
在java中一般使用集合来实现缓存,先看简单的例子:
JavaCache.java
<span style="font-size:24px;">/**
* Java中缓存的基本实现示例
*/
public class JavaCache {
public static Map<String,Object> map = new HashMap<String,Object>();
/**
* 从缓存中获取值
* @param key 设置时候的key值
* @return key对应的Value值
*/
public static Object getValue(String key){
//先从缓存里面取值
Object obj = map.get(key);
//判断缓存里面是否有值
if(obj == null){
//如果没有,那么就去获取相应的数据,比如读取数据库或者文件
//这里只是演示,所以直接写个假的值
obj = key+",value";
//把获取的值设置回到缓存里面
map.put(key, obj);
}
//如果有值了,就直接返回使用
return obj;
}
}</span>
<span style="font-size:24px;">Client.java</span>
<span style="font-size:24px;">public class Client {
public static void main(String[] args) {
JavaCache.map.put("a", "a");
JavaCache.map.put("b", "b");
JavaCache.map.put("c", "c");
JavaCache.map.put("d", "d");
System.out.println(JavaCache.getValue("a"));
System.out.println(JavaCache.getValue("b"));
System.out.println(JavaCache.getValue("e"));
}
}</span>
<span style="font-size:24px;">运行结果:</span>
<span style="font-size:24px;">a
b
e,value</span>
<span style="font-size:24px;">在map集合并没有存放key为"e"对应的值</span>
<span style="font-size:24px;">代码如下:</span>
<span style="font-size:24px;"></span>
<span style="font-size:24px;"></span>
<span style="font-size:24px;">if(obj == null){
System.out.println("zou");
//如果没有,那么就去获取相应的数据,比如读取数据库或者文件
//这里只是演示,所以直接写个假的值
obj = key+",value";
//把获取的值设置回到缓存里面
map.put(key, obj);
}</span>
然后保存大哦哦map集合中,下次获取key为"e"的值就在map集合中了,
缓存的基本实现:
1:定义一个存放缓存数据的容器
2:先从缓存里面取值
3:判断缓存里面是否有值
4:如果有值了,就直接使用这个值
5:如果没有,那么就去获取相应的数据,或者是创建相应的对象
6:把获取的值设置回到缓存里面
现在利用缓存模拟单例的实现
Singleton.java
<span style="font-size:24px;">/**
* 使用缓存来模拟实现单例
*/
public class Singleton {
/**
* 定义一个缺省的key值,用来标识在缓存中的存放
*/
private final static String DEFAULT_KEY = "One";
/**
* 缓存实例的容器
*/
private static Map<String,Singleton> map = new HashMap<String,Singleton>();
/**
* 私有化构造方法
*/
private Singleton(){
//
}
public static Singleton getInstance(){
//先从缓存中获取
Singleton instance = (Singleton)map.get(DEFAULT_KEY);
//如果没有,就新建一个,然后设置回缓存中
if(instance==null){
instance = new Singleton();
map.put(DEFAULT_KEY, instance);
}
//如果有就直接使用
return instance;
}
}</span>
上面就是使用缓存来实现单例模式的,也能确保类的实例仅有一个
现在从安全问题考虑单例的二种现实方式
1:饿汉式 这种是没有线程安全问题的,因为在getInstance()方法中直接返回了SingleTon对象,线程安全产生的条件是多个线程执行了同一段代码,但是饿汉式只有一行代码,所以不存在线程安全问题
2:懒汉式会出现安全问题,那我们分析分析怎么会出现安全问题
主要代码如下:
<span style="font-size:24px;">public static SingleTon getInstance(){
if(null==instance){
instance = new SingleTon();
}
return instance;
}</span>
<span style="font-size:24px;">
</span>
比如说有A,B线程同时调用getInstance()方法
A线程执行到了 new SingleTon();但是并没有赋值给instance,这个时候刚好有个B线程进来,因为new的对象没有赋值给instance变量,所以instance还是为null,因此B线程还是会走if语句,那么这个时候A和B都是new SingleTon();对象并赋值给instance,那就是创建了2个对象了,和单例模式的定义并不符合,这就是懒汉式导致的线程安全问题,既然出现了问题,那么我们就想办法解决,
在线程中解决安全问题通常有2种方法
1:使用同步代码块
2:使用同步方法
如果使用同步方法解决安全问题,这很简单直接在getInstance()方法中添加synchronized关键字就可以,代码如下:
<span style="font-size:24px;">public synchronized static SingleTon getInstance(){
if(null==instance){
instance = new SingleTon();
}
return instance;
}</span>
<span style="font-size:24px;">通过加一个关键字synchronized就轻而易举的解决了线程安全问题,问题解决了,那有没有比这更高效的方法呢?答案是有</span>
<span style="font-size:24px;">双重检查加锁判断比使用同步方法更高效</span>
<span style="font-size:24px;">所谓双重检查加锁机制,指的是:并不是每次进入getInstance()方法都需要同步,而是先不同步,进入方法后,先检查实例对象是否存在,如果不存在才进入下面的同步代码块,这是第一次检查,进入同步代码块后,再次检查实例对象是否存在,如果不存在,就在同步的情况下创建实例对象,这是第二次检查,这样一来,就只要同步一次了,从而减少了多次在同步情况下进行判断浪费时间</span>
<span style="font-size:24px;">
</span>
<span style="font-size:24px;">这就是双重检查加锁比同步方法效率更高德原因</span>
<span style="font-size:24px;">
</span>
<span style="font-size:24px;">代码如下:</span>
<pre class="java" name="code"><span style="font-size:24px;">public static Singleton getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized(Singleton.class){
//再次检查实例是否存在,如果不存在才真的创建实例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}</span>
<span style="font-size:24px;">总结:使用双重检查加锁只需要同步一次创建了对象后,其他时候就不需要同步了,直接返回创建的对象实例即可!</span>