设计模式之单例模式
什么时候用单例模式
- 在程序开发过程中有些对象我们可能只需要一个比如:配置文件,工具类,线程池,缓存,日志对象等.
常见的场景:
- windows的T Manager(任务管理器)就是很典型的单例模式。
- Windows的recyle bin(回收站)也是典型的单例模式。
- 项目中读取配置文件的类。
- 应用程序的日志应用,一般是由于共享的日志文件一直处于打开状 — 态,只能由一个实例去操作,否则难以追加。
- 网站的计数器,也是单利模式,否则难以实现同步
- 数据库连接池,因为数据库连接是一种数据库资源。
- 在Spring中每个bean默认都是单例的,这样做的优点是容易管理
- SpringMVC/struts1中的控制器对象也是单一的。
为什么要用单例模式
- 如果创造出多个实例,就会导致许多问题,比如占用过多资源,资源不一致等。
分类
- 单例模式:饿汉模式,懒汉模式,双重检测锁式,静态内部类式,枚举单例。
单例模式怎么用
饿汉模式:
(线程安全,调用,但是不能延时加载)
package Singleton;
/**
* 单例模式:只需要一个对象。
*
* 作用:保证整个应用程序中某个实例只有一个。
*/
public class Singleton {
//第一步将构造方法私有化--外界无法创建多个实例
private Singleton(){
}
//第二步,私有化构造方法之后无法创建实例。则应该创建一个唯一实例
//第三步,将singleton私有化--为了安全。
//当类加载时就会创建static singleton实例 (不管是否使用这个实例都已经加载,只要类加载了就会加载,所以称为饿汉模式)
private static Singleton singleton = new Singleton();
//第四步提供一个用于获取实例的方法
//第五步将其设置为类所有 ---static。否则无法访问
public static Singleton getInstance(){
return singleton;
}
}
//**这里不需要加synchronized 是因为当我们创建这个对象的时候,就立刻加载(没有延时加载的优势),在类加载器,加载时是天然的线程安全的。不需要同步化,调用的效率就高。**
懒汉模式,线程不安全:
/**
* 单例模式
*
* 懒汉模式,线程不安全
*/
public class Singleton_l {
// 第一步将构造方法私有化--外界无法直接创建多个实例
private Singleton_l() {
}
// 第二步,私有化构造方法之后无法创建实例。则应该创建一个唯一实例
// 第三步,将singleton私有化--为了安全。
private static Singleton_l Singleton_l;
//只有调用getInstance时才会加载
public static Singleton_l getInstance() {
if (Singleton_l == null) {
Singleton_l = new Singleton_l();
}
return Singleton_l;
}
}
//线程不安全,如果A对象在调用getInstance()方法判断为空的时候挂起,B对象调用getInstance()new了一个新的Singleton对象,b挂起,A开始执行new一个新的对象,出现两个对象。
懒汉模式线程安全
public class Singleton {
private static Singleton instance;
private Singleton (){}
//加synchronized关键字
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
//加synchronized关键字可以实现线程安全。但是调用效率低。
双重校验锁:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
静态内部类的实现:
//单例模式应该注意,线程安全,懒加载,调用效率高。静态内部类的实现就线程安全,懒加载,调用效率高
//当我们初始化类的时候,并不会立即初始化他的静态内部类,当我们调用getIntance()才会加载静态内部类。从而实现延时加载。
//类加载的时候是线程安全的也实现了线程安全。
public class SingletonDemo {
//定义一个静态的内部类。
private static class SingletonIntence {
//intance 加了static final 类型之后保证内存中只有一个这样的实例存在而且只能被赋值一次从而保证线程安全。
private static final SingletonDemo intance = new SingletonDemo();
}
private SingletonDemo() {
}
public static SingletonDemo getIntance() {
return SingletonIntence.intance;
}
}
枚举的实现:
//优点:实现简单,枚举本生就是单例,由jvm从根本上提供保证,避免通过反射和反序列的漏洞
//缺点:无延迟加载
public enum SingletonDemo {
// 定义一个枚举元素,代表一个单例
INSTANCE;
// 添加需要的操作
public void siglentonoperation() {
}
}
通过反射破解:(不包含枚举实现)
//测试反射破解单利模式
public class Client {
public static void main(String[] args) {
// 懒汉模式,线程安全的测试
Singleton_2 s1 = Singleton_2.getInstance();
Singleton_2 s2 = Singleton_2.getInstance();
System.out.println(s1==s2);
try {
//通过反射的方式直接调用私有构造器
Class<Singleton_2> clazz = (Class<Singleton_2>) Class.forName("Singleton.Singleton_2");
Constructor<Singleton_2> c = clazz.getDeclaredConstructor(null);//获取构造器
c.setAccessible(true);//跳过权限的检查不然不能访问私有的
Singleton_2 s3 = c.newInstance();
Singleton_2 s4 = c.newInstance();
System.out.println(s3==s4);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//false
//true
防止反射破解
public class Singleton {
private static Singleton instance;
private Singleton (){
if(instance!=null)
{
//通过判断instance!=null抛出异常,防止反射破解
throw new RuntimeException();
}
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
多线程测试
//CountDownLatch 是一个线程同步辅助类
import java.util.concurrent.CountDownLatch;
public class Client_Test {
public static void main(String[] args) {
long start = System.currentTimeMillis();
int threadnumber = 10;
final CountDownLatch count = new CountDownLatch(threadnumber);
//测试singleton_2单例10个线程创建1000000个对象的耗时
for (int i = 0; i < threadnumber; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
Object o = Singleton_2.getInstance();
}
count.countDown();//执行完成减去1
}
}).start();
}
try {
count.await();//main线程阻塞,当计数器变为0.才会执行
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start));
}
}
//通过测试可以得出上面几个单例模式的效率
总结:
如何选用:
单例对象,占用资源少,不需要延时加载:
- 枚举方式好于饿汉式
单例对象,占用资源多,需要延时加载:
- 静态内部类好于懒汉式
单例模式的优点:
- 由于单例只生成一个实例,减少系统的性能开销,当一个对象的产生比较消耗资源时,则可以在应用启动时直接产生一个对象,然后永远的驻留内存。