单例模式:
为什么用单例模式?什么时候用?怎么用?
单例模式好处:
①:单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能;
②:当想实例化的时候,必须要记住使用相应的获取对象方法,而不是使用new;
③:单例模式使用场景:需要频繁的进行创建和销毁的对象、创建对象和耗时过多或者消耗资源过多(即:重量级对象),但又经常使用到的对象。如:工具对象、频繁访问数据库或者文件对象(如:数据源、session工厂等)
下面我们对单例模式详细分析(写的不对的地方,希望大家来指正)
1):饿汉式(静态常量)
优点:写法简单,就是在类装载的时候就完成实例化。避免了线程同步问题
缺点:在类装载时的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终都没用到过这个类,则会造成内存的浪费。
总结:这种单例模式可用,可能造成内存浪费
package singleton;
public class EvilMan1 {
public static void main(String[] args) {
//测试
Evil evil1 = Evil.getInstance();
Evil evil2 = Evil.getInstance();
System.out.println(evil1.hashCode());
System.out.println(evil2.hashCode());
//输出 366712642 366712642
}
}
//饿汉式静态变量
class Evil{
//1.构造器私有化,外部能new
private Evil() {
}
//2.本类内部创建对象实例
private final static Evil evil = new Evil();
//3.提供一个共有的静态方法,返回实例对象
public static Evil getInstance() {
return evil;
}
}
2):饿汉式(静态代码块)
优点:这种方式和上面的方式类似,只不过将类实例化的过程放在静态代码块中,优缺点和上面一样
package singleton;
public class EvilMan2 {
public static void main(String[] args) {
//测试
Evil evil1 = Evil.getInstance();
Evil evil2 = Evil.getInstance();
System.out.println(evil1.hashCode());
System.out.println(evil2.hashCode());
}
}
//饿汉式静态变量
class Evil2{
//1.构造器私有化,外部能new
private Evil2() {
}
//2.本类内部创建对象实例
private static Evil2 evil;
//在静态代码方法中返回实例对象
static {
evil = new Evil2();
}
//3.提供一个共有的静态方法,返回实例对象
public static Evil2 getInstance() {
return evil;
}
}
3):懒汉式(线程不安全)
特点:起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个进程进入了判断语句,还没来得急往下执行,另一个线程也通过了这个判断语句,这是便会产生多个实例。所以在多线程环境下不可以使用这种模式
总结:在实际开发中,不要用这种模式
package singleton;
public class EvilMan3 {
public static void main(String[] args) {
//测试
Evil evil1 = Evil.getInstance();
Evil evil2 = Evil.getInstance();
System.out.println(evil1.hashCode());
System.out.println(evil2.hashCode());
}
}
//饿汉式(线程不安全)
class Evil3{
//2.本类内部创建对象实例
private static Evil3 evil;
private Evil3() {}
//3.提供一个共有的静态方法,返回实例对象
public static Evil3 getInstance() {
if (evil == null) {
evil = new Evil3();
}
return evil;
}
}
4):懒汉式(线程安全,同步方法)
总结:如果在方法中加上synchronized,倒是可以解决线程不安全问题,但是效率太低,每个线程执行到getInstance()方法都要进行同步,而这方法只执行一次就够了,后面想要获得该类实例,直接return就可以了。方法进行同步效率太低。在实际开发中,不推荐使用
5):懒汉式(线程安全,同步代码块)
总结:要是同步代码块,如下面,就不同线程同步了,类似于懒汉第一种,开发中,不能使用
class Evil3{
//2.本类内部创建对象实例
private static Evil3 evil;
private Evil3() {}
//3.提供一个共有的静态方法,返回实例对象
public static Evil3 getInstance() {
if (evil == null) {
synchronized (Evil3.class) {
evil = new Evil3();
}
}
return evil;
}
}
6):双重检查
总结:我们进行了两次if(evil == null)判断,这样就可以保证线程安全,在使用过程中只能执行一次实例化代码操作,后面在访问时,直接返回实例化对象,也避免了反复方法景行同步。其次,线程安全,延时加载,效率较高,在实际开发过程中,推荐使用这种开发模式
class Evil4{
private static volatile Evil4 evil;
private Evil4() {}
public static Evil4 getInstance() {
if (evil == null) {
synchronized (Evil3.class) {
if (evil == null) {
evil = new Evil4();
}
}
}
return evil;
}
}
7):静态内部类
总结
1):这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
2):静态内部类方式在Evil3类呗装载时并不会立即实例化,而是在需要实例化时,调用getInstance()方法,才会装载Evil3Instance类,从而完成Evil3的实例化。
3):类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,再累进行初始化时,别的线程时无法进入的。
4):避免了线程不安全,利用静态内部类特点实现延时加载,效率高
5):推荐使用
class Evil3{
private static volatile Evil3 evil;
private Evil3() {}
//写一个静态内部类,该类有一个静态属性Evil3
public static class Evil3Instance{
private static final Evil3 I_EVIL3 = new Evil3();
}
//提供一个静态的公有方法
public static synchronized Evil3 getInstance() {
return Evil3Instance.I_EVIL3;
}
}8):枚举
总结
1):不仅能避免多线程同步问题,而且还能防止反序列化重新创建对象
3):推荐使用
public class EvilMan4 {
public static void main(String[] args) {
//测试
Evil4 evil1 = Evil4.instance;
Evil4 evil2 = Evil4.instance;
System.out.println(evil1.hashCode());
System.out.println(evil2.hashCode());
evil1.getInstance();
}
}
enum Evil4{
instance;
public void getInstance() {
System.out.println("ok");
}
}总结:对于单例模式的八种模式,推荐饿汉式、双重检测、静态内部类、枚举