提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、单例模式是什么?
1.官方解释
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
——百度百科
2.个人理解
一种最简单的设计模式,目标:只创建一次对象,在程序中其它需要用到相同功能的地方,只调用这个对象而不创建新的对象,保证了数据的一致性(例如:Java中的日历类)
3.分类
1.饿汉式:
饿汉式:在程序加载时就创建好实例,等待被调用。
优点:采用以空间换时间的策略。由于创建对象是一个比较慢的过程,提前创建好实例(对象)可以大大节省程序执行的时间。
缺点:如果不使用这个实例会浪费一定的空间资源(反驳:1.一般被设计成单例模式的对象不使用的可能性不大;2.如今硬件存储容量较大,对性能方面的需求:时间>空间)
2.懒汉式:
懒汉式:在程序调用时才创建实例。
优点:不使用就不创建节省内存空间。
缺点:拖慢程序运行速度。
二、详细说明
下面以一个简单的Java代码例子对设计过程进行分解解释。
目标:只创建一次对象
1.饿汉式
准备工作:
创建Dog类(单例模式)和Test测试类
Dog类代码如下(示例):
//Dog类
public class Dog {
public Dog() {
}
}
Test类代码如下(示例):
//Test类
public class Test {
public static void main(String[] args) {
Dog d1=new Dog();
Dog d2=new Dog();
System.out.println(d1);
System.out.println(d2);
Dog d3=d2;
System.out.println(d3);
}
}
打印地址:相同表示是同一对象,不相同表示不是同一对象。
运行结果:
问题:new出来的两个对象地址不相同(表是不是同一个对象),不满足要求。
尝试解决:将public Dog()改为:private Dog()
Dog类代码如下(示例):
//Dog类
public class Dog {
private Dog() {
}
}
新的问题:Test类里面不能再new Dog();
尝试解决:将Test类里面的new Dog();放入Dog类;
Dog类代码如下(示例):
//Dog类
public class Dog {
private Dog(){
}
public Dog getDog(){
Dog dog=new Dog();
return dog;
}
}
Test类代码如下(示例):
//Test类
public class Test {
public static void main(String[] args) {
Dog d1=Dog.getDog();
System.out.println(d1);
}
}
新的问题:没有暴露外部访问(Test类不能访问Dog类)
尝试解决:将Dog类里面的public Dog getDog()设为静态便于访问;
Dog类代码如下(示例):
//Dog类
public class Dog {
private Dog(){
}
public static Dog getDog(){
Dog dog=new Dog();
return dog;
}
}
解决!
再次测试
Test类代码如下(示例):
//Test类
public class Test {
public static void main(String[] args) {
Dog dog1 = Dog.getDog();
Dog dog2 = Dog.getDog();
System.out.println(dog1);
System.out.println(dog2);
}
}
新的问题:地址不一致(对象不是同一个)
尝试解决:将“Dog dog=new Dog();”写到getDog(){}外面
Dog类代码如下(示例):
//Dog类
public class Dog {
public Dog dog=new Dog();
private Dog(){
}
public static Dog getDog(){
return dog;
}
}
新的问题:需要一个静态返回值,但是返回值不是静态
尝试解决:将“Dog dog=new Dog();”设置为静态;
Dog类代码如下(示例):
//Dog类
public class Dog {
public static Dog dog=new Dog();
private Dog(){
}
public static Dog getDog(){
return dog;
}
}
解决!
测试
Test类代码如下(示例):
//Test类
public class Test {
public static void main(String[] args) {
Dog dog1 = Dog.getDog();
Dog dog2 = Dog.getDog();
System.out.println(dog1);
System.out.println(dog2);
}
}
结果:
嗯~貌似实现了目标
再次测试
Test类代码如下(示例):
//Test类
public class Test {
public static void main(String[] args) {
Dog dog1 = Dog.getDog();
Dog.dog = null;
Dog dog2 = Dog.getDog();
System.out.println(dog1);
System.out.println(dog2);
Dog dog3 = Dog.getDog();
System.out.println(dog3);
}
}
结果:
新的问题:Dog类里面“public static Dog dog=new Dog();”
public导致Test类里面可以改变dog
尝试解决:将public改为private
Dog类代码如下(示例):
//Dog类
public class Dog {
private static Dog dog=new Dog();
private Dog(){
}
public static Dog getDog(){
return dog;
}
}
结果:Test类中不能再“ Dog.dog = null;”
再次测试:
Test类代码如下(示例):
//Test类
public class Test {
public static void main(String[] args) {
Dog dog1 = Dog.getDog();
Dog dog2 = Dog.getDog();
System.out.println(dog1);
System.out.println(dog2);
Dog dog3 = Dog.getDog();
System.out.println(dog3);
}
}
结果:
解决!
新的测试:尝试将上述Test类里面的问题(“Dog.dog = null;”)加入Dog类
Dog类代码如下(示例):
//Dog类
public class Dog {
private static Dog dog=new Dog();
private Dog(){
}
public static Dog getDog(){
Dog.dog = null;
return dog;
}
}
测试:
Test类代码如下(示例):
//Test类
public class Test {
public static void main(String[] args) {
Dog dog1 = Dog.getDog();
Dog dog2 = Dog.getDog();
System.out.println(dog1);
System.out.println(dog2);
Dog dog3 = Dog.getDog();
System.out.println(dog3);
}
}
结果:
新的问题:在Dog类里面仍然可以改变dog
尝试解决:在“private static Dog dog=new Dog();”里面加final ,表示最后的不能修改的
结果:该行代码失效
去掉该行代码!
Dog类代码如下(示例):
//Dog类
public class Dog {
private final static Dog dog=new Dog();
private Dog(){
}
public static Dog getDog(){
return dog;
}
}
再次测试:
Test类代码如下(示例):
//Test类
public class Test {
public static void main(String[] args) {
Dog dog1 = Dog.getDog();
Dog dog2 = Dog.getDog();
System.out.println(dog1);
System.out.println(dog2);
Dog dog3 = Dog.getDog();
System.out.println(dog3);
}
}
结果:
完美解决!
以上Dog类就是一个完整的饿汉式单例模式!
总结:饿汉式单例模式代码如下
//Dog类
public class Dog {
private final static Dog dog=new Dog();
private Dog(){
}
public static Dog getDog(){
return dog;
}
}
2.懒汉式
问题:饿汉式提前创建对象在不使用对象时会浪费内存空间
尝试解决:使用的时候再创建(调用时判断,如果没有再创建)
改造Dog类:
Dog类代码如下(示例):
//Dog类
public class Dog {
private final static Dog dog=null;
private Dog(){
}
public static Dog getDog(){
dog=new Dog();
return dog;
}
}
这里会报错:原因是“final”与“ dog=new Dog();”冲突
修改Dog,删除final
Dog类代码如下(示例):
//Dog类
public class Dog {
private static Dog dog=null;
private Dog(){
}
public static Dog getDog(){
dog=new Dog();
return dog;
}
}
测试:
Test类代码如下(示例):
//Test类
public class Test {
public static void main(String[] args) {
Dog dog1 = Dog.getDog();
Dog dog2 = Dog.getDog();
System.out.println(dog1);
System.out.println(dog2);
Dog dog3 = Dog.getDog();
System.out.println(dog3);
}
}
结果:
貌似实现了目标
潜在问题:
涉及线程调度时第一个线程(Thread1)进来执行到第11行代码结束时间片刚好用完,但是还没有执行第14行,于是在第11行与第14行代码之间等待;此时线程2(Thread2)进来执行,相同的原因停留在在第11行与第14行代码之间等待;之后线程1(Thread1)重新获得时间片执行后续代码;而线程2(Thread2)苏醒时会再执行一次后续代码,从而导致不符合单例要求。
就上述问题换一种场景(多线程)再次测试
Test类代码如下(示例):
//Test类(多线程)
public class Test2 {
public static void main(String[] args) {
Thread thread = new Thread() {
public void run() {
Dog dog = Dog.getDog();
int hashCode = dog.hashCode();
System.out.println(Thread.currentThread().getName() + ":" + hashCode);
}
};
thread.start();
Thread thread2 = new Thread() {
public void run() {
Dog dog = Dog.getDog();
int hashCode = dog.hashCode();
System.out.println(Thread.currentThread().getName() + ":" + hashCode);
}
};
thread2.start();
}
}
结果:
新的问题:可以看到两个线程所创建的对象的hashCode值不一样,那么也就代表如果多线程调用该单例模式则会出现线程安全问题。
尝试解决:加个“synchronized”在线程1(Thread1)进来后锁定,保证线程2(Thread2)不能进入
Dog类代码如下(示例):
//Dog类
public class Dog {
private static Dog dog=null;
private Dog(){
}
public static synchronized Dog getDog(){
if(dog==null){
dog=new Dog();
}
return dog;
}
}
再次测试
Test类代码如下(示例):
//Test类(多线程)
public class Test2 {
public static void main(String[] args) {
Thread thread = new Thread() {
public void run() {
Dog dog = Dog.getDog();
int hashCode = dog.hashCode();
System.out.println(Thread.currentThread().getName() + ":" + hashCode);
}
};
thread.start();
Thread thread2 = new Thread() {
public void run() {
Dog dog = Dog.getDog();
int hashCode = dog.hashCode();
System.out.println(Thread.currentThread().getName() + ":" + hashCode);
}
};
thread2.start();
}
}
结果:
解决!
以上Dog类就是一个完整的懒汉式单例模式!
总结:懒汉式单例模式代码如下
//Dog类
public class Dog {
private static Dog dog=null;
private Dog(){
}
public static synchronized Dog getDog(){
if(dog==null){
dog=new Dog();
}
return dog;
}
}
详细学习请参考:
https://blog.csdn.net/qq_41286145/article/details/107357604?ops_request_misc=%7B%22request_id%22%3A%22169320979416800215034538%22%2C%22scm%22%3A%2220140713.130102334…%22%7D&request_id=169320979416800215034538&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-2-107357604-null-null.268%5Ev1%5Ekoosearch&utm_term=%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450