常见的设计模式(单例模式&工厂模式)

时间:2024-10-13 10:40:40

2.1 概念

  •  只有在你需要的时候,进行调用才会实例化。

2.2 示例

下面给大家看下五种不同懒汉模式他们之间的区别。

 

  • 第一种方法:在多线程访问时存在线程问题

 首先可以先把线程休眠哪里的代码去掉,去执行该代码是基本上没有出现线程问题,因为在绝对的速度面前线程是安全的。

 

 但是将线程睡眠时间加上,那么就会出现线程问题,所以我们就可以用到另外一种。

  1. package ;
  2. /**
  3. * 单例懒汉模式:有线程问题
  4. * @author zjjt
  5. *
  6. */
  7. public class SingletonDemo03 {
  8. //懒汉模式
  9. //先私有化构造函数,无法实例化出来
  10. private SingletonDemo03() {
  11. //线程休眠
  12. try {
  13. (100);
  14. } catch (Exception e) {
  15. ();
  16. }
  17. }
  18. private static SingletonDemo03 dm02=null;
  19. //我们解决这种问题就使用到synchronized
  20. //因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
  21. public static synchronized SingletonDemo03 getInstance() {
  22. //判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
  23. if(dm02==null) {
  24. dm02=new SingletonDemo03();
  25. }
  26. return dm02;
  27. }
  28. //测试:
  29. public static void main(String[] args) {
  30. for (int i = 0; i < 100; i++) {
  31. new Thread(()->{
  32. (().hashCode());
  33. }).start();
  34. }
  35. }
  36. }

 线程问题:

 

  

 

  • 第二种方法:给它加上同步锁(synchronized)

   利:

      解决了多线程的安全问题。

   弊:

    降低了程序的性能。每个线程都要去判断锁机制,那么会增加程序运行的负担,同时只要做判断,CPU都要处理,那么也会消耗CPU的资源即就是加同步会降低程序的性能

 

  1. package ;
  2. /**
  3. * 单例懒汉模式
  4. * @author zjjt
  5. *
  6. */
  7. public class SingletonDemo03 {
  8. //懒汉模式
  9. //先私有化构造函数,无法实例化出来
  10. private SingletonDemo03() {
  11. try {
  12. (100);
  13. } catch (Exception e) {
  14. ();
  15. }
  16. }
  17. private static SingletonDemo03 dm03=null;
  18. //我们解决这种问题就使用到synchronized
  19. //因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
  20. public static synchronized SingletonDemo03 getInstance() {
  21. //判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
  22. if(dm03==null) {
  23. dm03=new SingletonDemo03();
  24. }
  25. return dm03;
  26. }
  27. //测试:
  28. public static void main(String[] args) {
  29. for (int i = 0; i < 100; i++) {
  30. new Thread(()->{
  31. (().hashCode());
  32. }).start();
  33. }
  34. }
  35. }

 

  • 第三种方法:不在方法上使用同步锁,在方法里面使用同步锁  

   利:

   性能比第二种方法好。因为当一个线程进入同步锁里面,判断完以后,实例化,后面的线程之间返回该对象就可以啦!!!

 

  弊:

   也是最为致命的地方在于在我们判断是否为空的时候,假如第一个线程刚好走到判断是否为空和同步锁中间,我在下面代码标注//-----这块位置,突然被第二个线程抢了,于是第二个线程先进了同步锁,判断完了实例化了,然后由于第一个线程已经做完非空判断了,他也会走到同步锁里面,这样子又实例化了,所有这种方法也是存在多线程问题。

  1. package ;
  2. /**
  3. * 单例懒汉模式:有线程问题
  4. * @author zjjt
  5. *
  6. */
  7. public class SingletonDemo04 {
  8. //懒汉模式
  9. //先私有化构造函数,无法实例化出来
  10. private SingletonDemo04() {
  11. try {
  12. (100);
  13. } catch (Exception e) {
  14. ();
  15. }
  16. }
  17. private static SingletonDemo04 dm04=null;
  18. //我们解决这种问题就使用到synchronized(同步锁)
  19. //因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
  20. public static SingletonDemo04 getInstance() {
  21. //判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
  22. if(dm04==null) {
  23. //--------
  24. synchronized () {
  25. dm04=new SingletonDemo04();
  26. }
  27. }
  28. return dm04;
  29. }
  30. //测试:
  31. public static void main(String[] args) {
  32. for (int i = 0; i < 100; i++) {
  33. new Thread(()->{
  34. (().hashCode());
  35. }).start();
  36. }
  37. }
  38. }

 

  • 第四种方法:双重判断

    既保证了性能更加好一些,又保证了多线程的安全。

 

   流程:

    第一个线程----->第一个判断是否为空---->第二个线程抢了----->进入同步锁---->判断完以后出来实例化完成---->第一个线程才在进入同步锁--->进入第二个判断----->而对象不为空了----->不会进入第二个判断里面,直接返回对象。

 

  1. package ;
  2. /**
  3. * 双重判断
  4. * @author zjjt
  5. *
  6. */
  7. public class SingletonDemo05 {
  8. //先私有化构造函数,无法实例化出来
  9. private SingletonDemo05() {
  10. try {
  11. (100);
  12. } catch (Exception e) {
  13. ();
  14. }
  15. }
  16. private static SingletonDemo05 dm02=null;
  17. //我们解决这种问题就使用到synchronized(同步锁)
  18. //因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
  19. public static SingletonDemo05 getInstance() {
  20. //判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
  21. if(dm02==null) {
  22. //双重判断,当线程走到这里停止,换成了另外一个线程,但是他往下走还是会在判断一遍,所以这种方法更加安全。
  23. synchronized () {
  24. if(dm02==null) {
  25. dm02=new SingletonDemo05();
  26. }
  27. }
  28. }
  29. return dm02;
  30. }
  31. //测试:
  32. public static void main(String[] args) {
  33. for (int i = 0; i < 100; i++) {
  34. new Thread(()->{
  35. (().hashCode());
  36. }).start();
  37. }
  38. }
  39. }

 

  • 第五种方法: 静态内部类方法 

  我们在该方法中创建一个静态内部类,然后在该静态内部类中实例化SingletonDemo07,

我们在测试的时候调用getInstance() 方法,就会执行静态内部类,而静态内部类哪里加上了static,所以只会实例一次。

 

  1. package ;
  2. /**
  3. * 静态内部类
  4. * @author zjjt
  5. *
  6. */
  7. public class SingletonDemo07 {
  8. //先私有化构造函数,无法实例化出来
  9. private SingletonDemo07() {
  10. try {
  11. (100);
  12. } catch (Exception e) {
  13. ();
  14. }
  15. }
  16. //静态内部类,只有当我们调用他时,他才会执行。
  17. public static class SingletonDemoHolder{
  18. private final static SingletonDemo07 dm07=new SingletonDemo07();
  19. }
  20. public static SingletonDemo07 getInstance() {
  21. return SingletonDemoHolder.dm07;
  22. }
  23. //测试:
  24. public static void main(String[] args) {
  25. for (int i = 0; i < 100; i++) {
  26. new Thread(()->{
  27. (().hashCode());
  28. }).start();
  29. }
  30. }
  31. }

注:以上这五种方法都存在一个问题,就是虽然构造函数被私有化了,但是可以通过反射破话单例的私有化构造函数,所以我们可以使用第六种

  • 第六种方法:使用枚举型enum

    枚举型是由java之父创建的,枚举型里面压根没有构造函数,所以无法实例出该类。是jvm帮我们初始化的反射机制也无法破话,因为枚举型没有构造函数。

 

  1. package ;
  2. /**
  3. * 枚举类
  4. * @author zjjt
  5. *
  6. */
  7. public enum SingletonDemo06 {
  8. //枚举型是由java之父创建的,枚举型里面压根没有构造函数,所以无法实例出该类。
  9. //是jvm帮我们初始化的
  10. //反射机制也无法,因为枚举型没有构造函数
  11. INSTANCE;
  12. public String hello(String name) {
  13. return "hello " + name;
  14. }
  15. //测试
  16. public static void main(String[] args) {
  17. for (int i = 0; i < 100; i++) {
  18. new Thread(()->{
  19. (());
  20. }).start();
  21. }
  22. }
  23. }