背景:
最近上线的交易系统已经大面积推广使用了,然后首页商品信息为了加载速度快一些,我把商品都缓存在了redisl里面,这样就避免了总是去查询数据库,本以为这样就可以了,但是在下午高峰期的时候,还是会出现加载缓慢的情况。然后通过log查询,在调用redis的时候有将近200ms的耗时。经过进一步的分析,发现在每一个调用redis.getKey的地方都会去实例化一个redis操作对象,这个过程会伴随了连接创建。所以没想到会在这里“翻船”了。于是想着把redis客户端做成单例的,这样的话每次在调用redis的时候就不会频繁的再去实例化了。
单例模式特点:
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。使用Singleton的好处还在于一个可以节省内存,第二个避免频繁的创建和销毁大对象,适当的提高效率。所以可以看出来它具有这些特点:1,单例类只能有一个实例;2,单例类必须自己创建自己的唯一实例;3,单例类必须给所有其他对象提供这一实例。在很多操作中,比如建立目录、数据库连接都需要这样的单线程操作。还有, singleton能够被状态化; 这样,多个单态类在一起就可以作为一个状态仓库一样向外提供服务,比如,你要论坛中的帖子计数器,每次浏览一次需要计数,单态类能否保持住这个计数,并且能synchronize的安全自动加1,如果你要把这个数字永久保存到数据库,你可以在不修改单态接口的情况下方便的做到。在计算机系统中,操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。l另外我们熟悉的Windows的Task Manager(任务管理器)就是很典型的单例模式,你能打开两个windows task manager吗? Singleton模式看起来简单,使用方法也很方便,但是真正用好,是非常不容易,需要对Java的类 线程 内存等概念有相当的了解。
单利模式的几种常用实现:
通用的单例模式创建思想一般分为三步,第一步,首先使用private修改该类构造器,从而将其隐藏起来,避免程序自由创建该类实例;第二步,然后提供一个public方法获取该类实例,且此方法必须使用static修饰(调用之前还不存在对象,因此只能用类调用;第三部,最后该类必须缓存已经创建的对象,否则该类无法知道是否曾经创建过实例,也就无法保证只创建一个实例。为此,该类需要一个静态属性来保持曾经创建的实例。 Singleton模式看起来简单,使用方法也很方便,但是真正用好,是非常不容易,需要对Java的类线程内存等概念有相当的了解。
第一种:饿汉模式,他是一种比较形象的称谓。既然饿,那么在创建对象实例的时候就比较着急,于是在装载类的时候就创建对象实例。饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。
1 /**
2 * @author yk
3 * @version 1.0
4 * @describe 饿汉单例模式
5 * @date 09-29 18:13
6 */
7 public class EagerSingleton {
8 private static final EagerSingleton eagerSingleton = new EagerSingleton();
9 private EagerSingleton() {
10
11 }
12 public static EagerSingleton getInstance() {
13 return eagerSingleton;
14 }
15 }
16
17 /**
18 * @author yk
19 * @version 1.0
20 * @describe 静态块饿汉单例模式
21 * @date 09-29 18:13
22 */
23 public class StaticEagerSingleton {
24
25 private static final StaticEagerSingleton staticEagerSingleton;
26
27 static {
28 staticEagerSingleton = new StaticEagerSingleton();
29 }
30
31 private StaticEagerSingleton() {
32
33 }
34
35 public static StaticEagerSingleton getInstance() {
36 return staticEagerSingleton;
37 }
38 }
第二种:懒汉式其实是一种比较形象的称谓。既然懒,那么在创建对象实例的时候就不着急。会一直等到马上要使用对象实例的时候才会创建,懒人嘛,总是推脱不开的时候才会真正去执行工作,因此在装载对象的时候不创建对象实例。懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间,由于懒汉式的实现是线程安全的,这样会降低整个访问的速度,而且每次都要判断。
1 /**
2 * @author yk
3 * @version 1.0
4 * @describe 懒汉式,需要的时候才创建
5 * @date 09-29 18:19
6 */
7 public class LazySingleton {
8 //静态属性用来缓存创建实例
9 private static LazySingleton instance = null;
10 //私有构造方法避免程序自由创建实例
11 private LazySingleton() {
12 }
13 //静态公共方法用于取得该类实例
14 public static synchronized LazySingleton getLazySingletonInstance() {
15 if (instance == null) {
16 instance = new LazySingleton();
17 }
18 return instance;
19 }
20 }
第三种,双重检查,可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受很大的影响。它并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。它的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,如果被其他线程修改后,当前线程是可见的,从而确保多个线程能正确的处理该变量。
1 /**
2 * @author yk
3 * @version 1.0
4 * @describe panda-example
5 * @date 09-29 18:53
6 */
7 public class DoubleCheckedSingleton {
8 //被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
9 private volatile static DoubleCheckedSingleton instance = null;
10 //私有构造方法
11 private DoubleCheckedSingleton() {
12 }
13 //公共静态方法获取实例
14 public static DoubleCheckedSingleton getSingletonInstance() {
15 //先检查实例是否存在,不存在,在进行同步
16 if (instance == null) {
17 //同步块,线程安全的创建实例
18 synchronized (DoubleCheckedSingleton.class) {
19 //再次检查实例是否存在,如果不存在才真正的创建实例
20 if (instance == null) {
21 instance = new DoubleCheckedSingleton();
22 }
23 }
24 }
25 return instance;
26 }
27 }
这里,其实如果private volatile static DoubleCheckedSingleton instance = null; 不用volatile也不会有什么问题,但是这里为什么要用volatile呢?留下一点儿思考的空间。