Java单例模式深度解析:饿汉式与懒汉式实现技巧
摘要: 在Java编程中,单例模式是一种常用的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。本文深入探讨了饿汉式和懒汉式单例模式的实现方法,包括它们的特点、实现代码和适用场景。通过阅读本文,你将能够理解单例模式的核心要点,并掌握如何在实际项目中正确应用这两种单例模式。
关键词: Java单例模式,饿汉式,懒汉式,设计模式,线程安全
1. 单例模式概述
单例模式是一种创建型设计模式,它确保某个类只有一个实例,并提供一个全局访问点。这种模式在Java中广泛应用,如Runtime
类。
核心要点:
- 唯一实例: 类的实例必须是唯一的。
- 内部创建: 构造器必须私有化,防止外部创建实例。
- 全局访问: 提供一个公共的静态方法获取实例。
- 创建时机: 分为饿汉式和懒汉式。
2. 饿汉式单例模式
饿汉式单例模式在类加载时就创建实例,避免了线程同步问题。
2.1 实现方式
- 静态常量实例化
- 枚举类
- 静态代码块
2.2 代码示例
//1、静态常量直接实例化(代表jvm运行环境的Runtime类生成单例,采用的就是这种实现方式,简洁直观);
class HungrySigleton1 {
private HungrySigleton1(){
}
private final static HungrySigleton1 INSTANCE = new HungrySigleton1();
public static HungrySigleton1 getInstance(){
return INSTANCE;
}
}
//2、枚举类(最简洁);
enum HungrySigleton3{
INSTANCE;
private HungrySigleton3() {
}
}
//3、静态代码块(适合复杂的场景,如从配置文件读取所需参数)。
class HungrySigleton2 {
private HungrySigleton2(String info){
System.out.println(info);}
private static HungrySigleton2 INSTANCE =null;
static {
Properties prop = new Properties();
InputStream inStream = HungrySigleton2.class.getClassLoader().getResourceAsStream("project.properties");
try {
prop.load(inStream);} catch (IOException e) {
e.printStackTrace();}
String info =(String) prop.get("info");
//扩展:读取配置文件的几种方式 1、Properties继承自Hashtable 2、ResourceBundle 3、Spring种利用ApplicationContext加载xml文件
INSTANCE = new HungrySigleton2(info);
}
public static HungrySigleton2 getInstance(){
return INSTANCE;
}
}
public class Hungry{
public static void main(String[] args) {
HungrySigleton1 ins1 = HungrySigleton1.getInstance();
HungrySigleton2 ins2 = HungrySigleton2.getInstance();
HungrySigleton3 ins3 = HungrySigleton3.INSTANCE;
}
}
3. 懒汉式单例模式
懒汉式单例模式在第一次使用时才创建实例,可能需要处理线程同步问题。
3.1 实现方式
- 非线程安全版本
- 同步方法实现
- 同步代码块实现
- 双重检查锁定(DCL)
- 静态内部类
3.2 代码示例
class LazySigleton1 {
private LazySigleton1(){
}
private static LazySigleton1 INSTANCE =null;
public static LazySigleton1 getInstance(){
if(null == INSTANCE){
INSTANCE = new LazySigleton1();
}
return INSTANCE;
}
}
//2、懒汉式版本2(同步方法实现线程安全,但效率很低);
class LazySigleton2 {
private LazySigleton2(){
}
private static LazySigleton2 INSTANCE =null;
public synchronized static LazySigleton2 getInstance(){
if(null == INSTANCE){
try {
Thread.sleep(100);} catch (InterruptedException e) {
e.printStackTrace();}
INSTANCE = new LazySigleton2();
}
return INSTANCE;
}
}
//3、懒汉式版本2(同步代码块提供了一定效率,但存在线程安全问题);
class LazySigleton3 {
private LazySigleton3(){
}
private static LazySigleton3 INSTANCE =null;
public static LazySigleton3 getInstance(){
if(null == INSTANCE){
try {
Thread.sleep(100);} catch (InterruptedException e) {
e.printStackTrace();}
synchronized(INSTANCE){
INSTANCE = new LazySigleton3();
}
}
return INSTANCE;
}
}
//4、懒汉式版本2(DCL(Double Check Lock),线程安全效率较高,但由于synchronized无法避免jvm优化时可能出现的乱序执行,可能会遇到拿到的实例对象尚未被正确初始化的问题,这时可以利用volatile来解决);
class LazySigleton4 {
private LazySigleton4(){
}
private static volatile LazySigleton4 INSTANCE =null;
public static LazySigleton4 getInstance(){
if(null == INSTANCE){
try {
Thread.sleep(100);} catch (InterruptedException e) {
e.printStackTrace();}
synchronized(INSTANCE){
if(null == INSTANCE){
INSTANCE = new LazySigleton4();
}
}
}
return INSTANCE;
}
}
//5、静态内部类(内部类被加载和初始化时才会创建对象,即调用时)
class LazySigleton5 {
private LazySigleton5(){
}
private static class Inner{
private final static LazySigleton5 INSTANCE = new LazySigleton5();
}
public static LazySigleton5 getInstance(){
return Inner.INSTANCE;
}
}
public class Lazy {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService exec = Executors.newFixedThreadPool(2);
Callable task = new Callable(){
@Override
public Object call() {
LazySigleton1 instance = LazySigleton1.getInstance();
System.out.println(Thread.currentThread().getName()+instance);
return instance;
}
};
Future f1 = exec.submit(task);
Future f2 = exec.submit(task);
LazySigleton1 l1 = (LazySigleton1) f1.get(),l2 = (LazySigleton1) f2.get();
System.out.println("l1:" + l1);
System.out.println("l2:" + l2);
System.out.println(l1==l2);
exec.shutdown();
}
}
4. 优缺点对比
实现方式 | 优点 | 缺点 |
---|---|---|
饿汉式 | - 线程安全 - 简单直观 |
- 类加载时就创建实例,可能导致资源浪费 |
懒汉式非线程安全 | - 延迟实例化 - 节省资源 |
- 非线程安全 |
懒汉式同步方法 | - 线程安全 | - 效率低,每次访问都要同步 |
懒汉式同步代码块 | - 线程安全 - 效率较高 |
- 复杂的线程同步问题 |
双重检查锁定 | - 线程安全 - 高效 |
- 需要处理指令重排问题 |
静态内部类 | - 线程安全 - 延迟加载 |
- 代码复杂度较高 |
5. 流程图
graph TD
A[开始] --> B[检查实例]
B -->|不存在| C[创建实例]
B -->|存在| D[返回实例]
C --> D
6. 结语
单例模式是Java中一种非常重要的设计模式,适用于需要严格控制实例数量的场景。通过本文的介绍,你应该对饿汉式和懒汉式单例模式有了更深入的理解。希望这些信息能帮助你在实际开发中做出更好的设计决策。
思维导图:
graph LR
A[单例模式] --> B[饿汉式]
A --> C[懒汉式]
B --> D[静态常量]
B --> E[枚举类]
B --> F[静态代码块]
C --> G[非线程安全]
C --> H[同步方法]
C --> I[同步代码块]
C --> J[双重检查锁定]
C --> K[静态内部类]
Excel表格:
实现方式 | 优点 | 缺点 |
---|---|---|
饿汉式 | - 线程安全 - 简单直观 |
- 类加载时就创建实例,可能导致资源浪费 |
懒汉式非线程安全 | - 延迟实例化 - 节省资源 |
- 非线程安全 |
懒汉式同步方法 | - 线程安全 | - 效率低,每次访问都要同步 |
懒汉式同步代码块 | - 线程安全 - 效率较高 |
- 复杂的线程同步问题 |
双重检查锁定 | - 线程安全 - 高效 |
- 需要处理指令重排问题 |
静态内部类 | - 线程安全 - 延迟加载 |
- 代码复杂度较高 |
鼓励话语: 掌握单例模式,就像是掌握了控制Java世界的一把钥匙。如果你有更多的见解或者遇到了难题,不妨在评论区分享,让我们一起探讨,共同进步!