前言
看到这个题目,相信大家都知道了,Java中单例模式的写法有好几种,那么我们怎么去实现呢,还有我们为什么要使用单例模式呢,这是我们本文需要研究的问题。接下来闲话少说了,直接进入我们的正题“单例模式”。
1.什么是单例
一个类在整个系统中只有一个实例,而且这个实例是在类的内部通过一个private的构造方法构造的,外部不能调用其构造方法,只能获取它的实例。
2.目的
拿正常的系统来说,我们需要登录,登录之后呢,会看到一个在线登录人数的。这个登录人数是使用计数器做到的,如果同时好几个人同时登录系统的话,获取系统中的数据,然后都加1,然后存储到文件或者缓存或者数据库中。这样操作后续登陆的用户得到的在线人数,与实际的在线人数并不一致。所以这样,我们就需要把计数器设计成一个全局对象,所有的人都共用同一个数据,这样就避免了登录用户和在线人数不同的问题,这样就用到了我们所说的单例模式了。
单例模式能确保一个类只有一个唯一的实例,并向外部提供一个全局的访问点。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
3.单例模式的特点单例类只能有一个实例
单例类必须自己创建自己的唯一实现
单例类必须给其他对象提供这一实例
现在讲一下两种简单的单例模式:
1.懒汉式单例
懒汉模式,我们可以理解为,在我们使用这个对象的时候,判断内存中是否已存在该对象,如果不存在就立马创建,如果存在则不在创建,直接返回。
懒汉模式只在外部对象第一次请求对象的时候才去创建。
2.饥汉式单例
饿汉模式,我们可以理解为,在这个类加载的时候,对象就已经创建好了。
下面做一个对这两种单例模式的使用和优缺点,以及更正做简单的介绍。
创建线程类,使用for循环创建多线程,模拟高并发
public class MyThread extends Thread {
@Override
public void run() {
Singleton singleton = Singleton.getInstance();
//获取singleton对象的hashcode值
System.out.println(singleton.hashCode());
}
}
创建单例
1.懒汉模式(普通模式,只适合单线程,线程不安全,不可用)
public class Singleton {
private static Singleton singleton= null;
// 懒汉式单例类.在第一次调用的时候实例化自己private Singleton() {
}
public static Singleton getInstance() {
if (singleton== null) {
singleton= new Singleton();
}
return singleton;
}
}
使用多线程测试,打印一下,我们实例的hashcode值,看看我们拿到的是不是同一个对象。
我们看到,hashcode是不一样的,所以上面这种事线程不安全的,不能使用。
下面的介绍,暂且不提供结果展示了,自己亲身测试一下,会比较理解。
2.懒汉模式(synchronized同步代码块,线程安全,不推荐使用)
public class Singleton {
private static Singleton singleton= null;
// 懒汉式单例类.在第一次调用的时候实例化自己
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (singleton== null) {
singleton= new Singleton();
}
return singleton;
}
}
这种方式就是在第一种的情况下使用了同步锁(synchronized),每次通过getInstance方法得到singleton实例的时候都要去获取同步锁。大家都知道,加锁是比较耗时的,所以这种当时
3.懒汉模式(加synchronized同步锁时,前后两次判断实例是否存在(可行))
public class Singleton {
private static Singleton singleton=null;
private Singleton(){
}
public static Singleton getInstance(){
if(singleton==null){
synchronized(Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
在程序运行过程中,只有当singleton为null时,才需要我们获取同步锁,并创建一次实例。当实例已经被创建,则无需试图加锁。但是我们在单例中使用了双重的if判断,逻辑变得比较复杂,容易出错。
4.饥汉式模式(线程安全,可以使用)
public class Singleton {
private static Singleton singleton=new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return singleton;
}
}
只有在初始化静态的singleton对象时被创建一次。如果我们在Singleton类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例。因为没有延迟加载的效果,所以降低内存的使用率。
5.静态内部类(线程安全,可以使用)
public class Singleton {
private Singleton(){
}
private static class SingletonHolder{
private final static Singleton singleton=new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.singleton;
}
}
定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,由于是私有的属性,其他对象无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例,也不能在外部实例化对象。而且还达到了延迟加载的效果,在需要的时候再创建实例,很大程度的提高了内存的使用率。
说了这么多,做了这么多的实验,那么使用单例模式的好处又是什么呢?
1、控制资源的使用,通过线程同步来控制资源的并发访问;
2、控制实例产生的数量,达到节约资源的目的。
3、作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信。
单例模式就先讲这么多,我是个菜鸟,就能理解这么多了,各位大神,有什么错误还请指正一下,自己再做更正,实现更大的进步。