JAVA设计模式之一单例模式
单例模式
首先我们要知道什么是单例模式,单例模式的用法以及好处
- 单例模式,顾名思义就是只加载一个实例
- 好处,别的不说没有并发性,不需要考虑线程安全,安全私有化不能随意去实例
- 用法有好几个,分别是饿汉式,懒汉式,饿汉改进式等
饿汉式 SingletonDemo01.java
效率高,不能延时加载
package 单例模式;
//饿汉式单例
public class SingletonDemo01 {
//1.私有化构造器
private SingletonDemo01() {}
//2.类的初始化的时候立即加载对象,不管用不用全都要所以很,不用考虑并发性线程安全
private static SingletonDemo01 instance=new SingletonDemo01();
//提供获取该对象的方法,没有synchronized,效率高
public static SingletonDemo01 getInstance() {
return instance;
}
}
class Test{
public static void main(String[] args) {
// TODO 自动生成的方法存根
SingletonDemo01 instance=SingletonDemo01.getInstance();
SingletonDemo01 instance2=SingletonDemo01.getInstance();
System.out.print(instance==instance2); //true,的确是单例
}
}
使用场景:对象功能简单且使用对象次数少
懒汉式 SingletonDemo02.java
较比饿汉式的话效率低,但是可以合理分配空间,更灵活。需要考虑线程安全(可以延时加载)
package 单例模式;
//懒汉式单例
public class SingletonDemo2 {
//1.私有化构造器
private SingletonDemo2() {}
//2.类的初始化的时候不立即加载对象
private static SingletonDemo2 instance=null; //先为null节省空间,根据需要再实例
//提供获取该对象的方法,有synchronized,效率较低
public static synchronized SingletonDemo2 getInstance() { //不立即加载的话多个线程排队会有线程安全所以要有锁
if(instance==null) {
instance=new SingletonDemo2();
}
return instance;
}
}
class Test2{
public static void main(String[] args) {
// TODO 自动生成的方法存根
SingletonDemo2 instance=SingletonDemo2.getInstance();
SingletonDemo2 instance2=SingletonDemo2.getInstance();
System.out.print(instance==instance2); //true,的确是单例
}
}
DCL懒汉式 SingletonDemo3.java
较懒汉式相比引进了volatile避免指令的重排还有双重检测。synchronized锁的不是方法而是改为同步代码块会使得效率较高一些,但是仍然会有线程安全问题(偶尔有问题不建议使用)
package 单例模式;
//DCL懒汉式单例
public class SingletonDemo3 {
//1.私有化构造器
private SingletonDemo3() {}
//2.类的初始化的时候不立即加载对象
//volatile:防止极端情况在实例类的时候返回实例加重新实例,作用:能够在返回实例的时候其它实例失效
private volatile static SingletonDemo3 instance=null;
//提供获取该对象的方法,有synchronized,效率较低
public static SingletonDemo3 getInstance() { //不立即加载的话多个线程排队会有线程安全所以要有锁
if(instance==null) {
synchronized(SingletonDemo3.class) {
if(instance==null) { //双重检测
instance=new SingletonDemo3();
}
}
}
return instance;
}
//考虑极端问题:1.分配内存,2.执行构造方法,3.指向地址
}
class Test3{
public static void main(String[] args) {
// TODO 自动生成的方法存根
SingletonDemo3 instance=SingletonDemo3.getInstance();
SingletonDemo3 instance2=SingletonDemo3.getInstance();
System.out.print(instance==instance2); //true,的确是单例
}
}
饿汉改进式 SingletonDemo4.java
综合了前面的优点,可以延时加载而且调用效率高,线程安全
但是最重要的是这个可以利用反射来破坏掉private的字段然后实现多个实例,即使可以不断加判断来限制反射,但是反射也可以不停的去破坏判断的字段
package 单例模式;
//饿汉式单例
public class SingletonDemo4 {
//1.私有化构造器
private SingletonDemo4() {}
//2.静态内部类 只有在调用内部类才会加载实例,避免了饿汉开始加载
private static class InnerClass{
private static final SingletonDemo4 instance=new SingletonDemo4();
}
//提供获取该对象的方法,线程安全
public static SingletonDemo4 getInstance() {
return InnerClass.instance;
}
}
class Test4{
public static void main(String[] args) {
// TODO 自动生成的方法存根
SingletonDemo4 instance=SingletonDemo4.getInstance();
SingletonDemo4 instance2=SingletonDemo4.getInstance();
System.out.print(instance==instance2); //true,的确是单例
}
}
枚举单例 SingletonDemo5.java (推荐使用)
不能延时加载但是调用效率高,线程安全与饿汉式优缺点一样
底层原码有防止反射破坏枚举单例的代码
package 单例模式;
//枚举单例 安全效率高不能延时加载,但是底层原码有防止反射破坏枚举单例
public enum SingletonDemo5 {
INSTANCE;
public SingletonDemo5 getInstance() {
return INSTANCE;
}
}
class Test5{
public static void main(String[] args) {
// TODO 自动生成的方法存根
SingletonDemo5 instance=SingletonDemo5.INSTANCE;
SingletonDemo5 instance2=SingletonDemo5.INSTANCE;
System.out.print(instance==instance2); //true,的确是单例
}
}
学单例模式的初衷:
- 全局可以访问,优先共享资源访问,因为只有一个实例去调用
- 只要是合格的开发者都基本要掌握,众多原码都含有单例模式
- 通过5种单例模式能学会考虑线程安全等多方面发散思维
- 数据库可以以单例模式加数据连接池进行访问,为了了解单例对于数据库的访问有何好处?这里可在评论讨论讨论