1.单例模式
一、单例模式
二、总结
一、单例模式(Singleton)
1.什么是单例模式?
·单例类只能 有一个实例。
·单例类必须自己创建自己的唯一实例。
·单例类必须给所有其他对象提供 这一实例。
2.为什么会有单例模式?
·当您想控制实例数目,节省系统资源的时候会需要用到单例模式。
应用场景:
①Windows的任务管理器。一个Windows只能打开一个任务管理器;
②网站的计数器,一般也采用单例模式,否则难以同步。
③多线程的线程池一般也采用单例模式,这是由于线程池需要对池中的线 程进行管理
3.怎么使用单例模式?如何创建?(6种方式)
·饿汉式
①线程是安全的。—并不是加锁,且执行效率高。
因为类加载到内存后,就实例化一个单例,JVM来保证线程安全。(JVM保证每一个类被加载一次,既然加载内存是一次的话,那类是静态的情况下,加载到内存之后就马上开始初始化了。所以也保证初始化也是一次,故是线程安全的。)
②浪费内存资源。由于不管类用到与否,类加载的时候就完成了初始化。没有达到lazy loading的效果。
public class SingletonDemo {
private static final SingletonDemo INSTANCE = new SingletonDemo();//1.先创建出实例
private SingletonDemo() {};//2.构造方法设置成private,别人无法new出实例
public static SingletonDemo getInstance() {return INSTANCE;}//3.获取实例
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
SingletonDemo m1 = SingletonDemo.getInstance();
SingletonDemo m2 = SingletonDemo.getInstance();
System.out.println(m1==m2);//测试得到的实例都是同一个实例。
}
}
·懒汉式
虽然满足了按需初始化的目的,但是却带来了线程不安全。所以不支持多线程的环境,因为没有加锁。严格意义上来讲不算单例模式。
public class LazyLoadingDemo {
private static LazyLoadingDemo INSTANCE;//1.最开始没有做初始化操作;
private LazyLoadingDemo() {//2.构造方法设置为private,这样就new不出来;
}
public static LazyLoadingDemo getInstance() {//3.获取实例
if (INSTANCE== null) {
try {
Thread.sleep(1);
}catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new LazyLoadingDemo();//第一次调用getInstance方法之后如果判断是空,就初始化。
} //第二次再判断的时候已经发现INSTANCE不是空的了,则返回原来的INSTANCE。
return INSTANCE;
}
public void m() {
System.out.println("m");
}
/*
* 此处利用多线程来测试线程不安全。
* */
public static void main(String[] args) {
for (int i = 0; i<100; i++) {
new Thread(()->{
System.out.println(LazyLoadingDemo.getInstance().hashCode());//不同对象的哈希码是不同的。
}).start();
}
}
}
以下是懒汉式的前提下,满足线程的安全。—加synchronized关键字。
虽然可以支持多线程了,但是执行效率却降低了。
public class LazyLoadingDemo2 {
private static LazyLoadingDemo2 INSTANCE;//1.最开始没有做初始化操作;
private LazyLoadingDemo2() {//2.构造方法设置为private,这样就new不出来;
}
public static synchronized LazyLoadingDemo2 getInstance() {//3.添加synchronized关键字。由于每次线程会看有没有申请到锁,然后才进行操作,所以就会造成效率降低,但保证了线程安全。
if (INSTANCE== null) {
try {
Thread.sleep(1);
}catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new LazyLoadingDemo2();//第一次调用getInstance方法之后如果判断是空,就初始化。
} //第二次再判断的时候已经发现INSTANCE不是空的了,则返回原来的INSTANCE。
return INSTANCE;
}
}
·双重校验
①满足lazy loading
②支持多线程,保证线程安全。
③在多线程的情况下,保持高性能。
public class LazyLoadingDemo3 {
private static volatile LazyLoadingDemo3 INSTANCE;//1.最开始没有做初始化操作;需要加上volatile,防止重排(如果做了JIT优化的话)。
private LazyLoadingDemo3() {//2.构造方法设置为private,这样就new不出来;
}
public static LazyLoadingDemo3 getInstance() {//3.获取实例
if (INSTANCE== null) {//①第一遍检查,先判断INSTANCE是否为空,如果是空就上锁。
//双重检查
synchronized (LazyLoadingDemo3.class) {//②第二遍检查
if (INSTANCE == null) {
try {
Thread.sleep(1);
}catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new LazyLoadingDemo3();//第一次调用getInstance方法之后如果判断是空,就初始化。
} //第二次再判断的时候已经发现INSTANCE不是空的了,则返回原来的INSTANCE。
}
}
return INSTANCE;
}
}
·静态内部类
这种方式能达到双重校验方式一样的效果,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双重校验方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
原因:static可以实现类只加载一次,加载外部类的时候不会加载内部类,所以实现了lazy loading。
public class LazyLoadingDemo4 {//在加载的时候不会加载静态内部类,只有在调用内部类的时候才会去加载。
private LazyLoadingDemo4() {
}
private static class LdHolder {//1.在LazyLoadingDemo4中定义了一个静态内部类,然后在静态内部类中初始化了一个LazyLoadingDemo4。因为LazyLoadingDemo4的构造方法是private的。所以只有它的内部类才能new的出来。
private final static LazyLoadingDemo4 INSTANCE = new LazyLoadingDemo4();//2.在静态内部类中初始化了一个LazyLoadingDemo4
}
public static LazyLoadingDemo4 getInstance() {
return LdHolder.INSTANCE;//3.返回的是静态内部类里面的外部类的INSTANCE
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i =0; i<100; i++) {
new Thread(()->{
System.out.println(LazyLoadingDemo4.getInstance().hashCode());
}).start();
}
}
·枚举
它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
public enum Singleton {
INSTANCE; //1.直接先写好实例
public void whateverMethod() {
}
}
二、总结
1.单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
2.各类情况的推荐使用方式如下:
(1)一般情况下,不建议使用懒汉方式,建议使用饿汉方式。
(2)只有在要明确实现 lazy loading 效果时,才会使用第静态内部方式。
(3)如果涉及到反序列化创建对象时,可以尝试使用枚举方式。
(4)如果有其他特殊的需求,可以考虑使用双重校验方式。