Java 进阶——单例模式

本文介绍了Java中的单例模式,包括懒汉式、饿汉式和登记式三种实现方式,详细阐述了单例模式的概念、特点以及线程安全问题。单例模式常用于配置信息的统一管理,但需注意并发环境下的线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、单例模式概念及特点

        java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。

  单例模式有一下特点:

        1、单例类只能有一个实例。

  2、单例类必须自己自己创建自己的唯一实例。

        3、单例类必须给所有其他对象提供这一实例。

而这个特点正是我们构造单例模式的方法:

1)构造函数私有化;

2)创建一个自身的对象;

3)向外提供方法。


  单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

  正是由于这个特 点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或 文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。


二、单例模式的举例

1、饿汉方式的单例模式

public class Singleton1 {
/*构造函数私有化*/
private Singleton1(){
}
/*创建一个自身的对象*/
private static final Singleton1 instance = new Singleton1();
/*向外提供方法*/
public static Singleton1 getInstance(){
		return instance;
}
}

特点:线程安全 但效率比较低  一开始就要加载类new一个对象


2、懒汉方式的单例模式

//懒汉式单例类.在第一次调用的时候实例化 
 public class Singleton2 {
     //私有的默认构造子
     private Singleton2() {}
     //注意,这里没有final    
     private static Singleton2 single=null;
     //静态工厂方法 
     public synchronized  static Singleton2 getInstance() {
          if (single == null) {  
              single = new Singleton2();
          }  
         return single;
     }
 }

特点:Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
           事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。

     但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:


3、登记式单例类

//登记式单例类.
 //类似Spring里面的方法,将类名注册,下次从里面直接获取。
 public class Singleton3 {
     private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();
     static{
         Singleton3 single = new Singleton3();
         map.put(single.getClass().getName(), single);
     }
     //保护的默认构造子
     protected Singleton3(){}
     //静态工厂方法,返还此类惟一的实例
     public static Singleton3 getInstance(String name) {
         if(name == null) {
             name = Singleton3.class.getName();
             System.out.println("name == null"+"--->name="+name);
         }
         if(map.get(name) == null) {
             try {
                 map.put(name, (Singleton3) Class.forName(name).newInstance());
             } catch (InstantiationException e) {
                 e.printStackTrace();
             } catch (IllegalAccessException e) {
                 e.printStackTrace();
             } catch (ClassNotFoundException e) {
                 e.printStackTrace();
             }
         }
         return map.get(name);
     }
     //一个示意性的商业方法
     public String about() {    
         return "Hello, I am RegSingleton.";    
     }    
     public static void main(String[] args) {
         Singleton3 single3 = Singleton3.getInstance(null);
         System.out.println(single3.about());
     }
 }


饿汉式和懒汉式区别

从名字上来说,饿汉和懒汉,

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。


另外从以下两点再区分以下这两种方式:

1、线程安全:


饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。


2、资源加载和性能:

       饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成;

      而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。


什么是线程安全?

        如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

       或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。




### Java 高级进阶编程练习题及答案解析 以下是几个经典的 Java 高级进阶编程题目及其解答分析: #### 1. 判断三个对象是否互不相等 编写一段代码来判断 `prog18` 对象中的属性 `a`, `b`, 和 `c` 是否两两都不相等。 ```java public class Prog18 { String a, b, c; public static void main(String[] args) { Prog18 prog18 = new Prog18(); prog18.a = "apple"; prog18.b = "banana"; prog18.c = "cherry"; boolean areAllDifferent = !(prog18.a.equals(prog18.b)) && !(prog18.a.equals(prog18.c)) && !(prog18.b.equals(prog18.c)); System.out.println(areAllDifferent); // 输出 true 如果三者均不同 } } ``` 上述代码片段展示了如何验证三个字符串变量是否完全不同的逻辑[^1]。此方法适用于任何可比较的对象类型,而不仅仅是字符串。 --- #### 2. 实现线程安全的单例模式 设计一种机制,在多线程环境下确保某个类只有一个实例存在。 ```java public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 双重检查锁定 synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 创建唯一实例 } } } return instance; } } ``` 这段代码采用了双重检查锁的方式实现懒加载版本的单例模式,保证即使在高并发场景下也能正常工作[^3]。 --- #### 3. 使用 Lambda 表达式简化集合操作 给定一个整数列表,筛选出其中所有的偶数并打印出来。 ```java import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); List<Integer> evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(evenNumbers); // 打印 [2, 4, 6] } } ``` 这里运用了 Java 8 引入的流 API 来处理数据集合,使代码更加简洁优雅[^4]。 --- #### 4. 解析 JSON 数据结构 假设有一个简单的 JSON 字符串表示用户的个人信息,请将其转换成对应的 Java Bean 类型。 ```json { "name": "Alice", "age": 25, "email": "alice@example.com" } ``` 可以借助第三方库如 Jackson 或 Gson 完成序列化/反序列化的任务: ```java class User { public String name; public int age; public String email; } // 使用 Jackson 库进行反序列化 ObjectMapper mapper = new ObjectMapper(); String jsonInput = "{\"name\":\"Alice\",\"age\":25,\"email\":\"alice@example.com\"}"; User user = mapper.readValue(jsonInput, User.class); System.out.println(user.name); // Alice ``` 这种方法非常适合于 RESTful Web Service 开发过程中涉及到的数据交换需求[^5]。 --- #### 5. 并发控制下的计数器模拟 创建一个多线程环境下的共享资源访问案例——原子性递增计数器。 ```java import java.util.concurrent.atomic.AtomicInteger; public class CounterTest implements Runnable { private final AtomicInteger counter = new AtomicInteger(); @Override public void run() { for(int i=0;i<1000;i++) { counter.incrementAndGet(); // 原子操作 } } public static void main(String[] args) throws InterruptedException { CounterTest test = new CounterTest(); Thread t1 = new Thread(test), t2 = new Thread(test); t1.start();t2.start(); t1.join();t2.join(); System.out.println(test.counter.get()); // 接近但可能小于 2000 } } ``` 通过引入 `AtomicInteger` 替代普通的整型变量,有效解决了因竞争条件引发的一致性问题[^6]。 --- ### 总结 以上列举了几种典型的 Java 进阶技术应用场景,涵盖了从基础语法到现代框架的最佳实践。希望这些例子能够帮助开发者巩固理论知识的同时提高实际动手能力。 问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值