java/android中单例模式详解

单例模式介绍

一、什么是单例模式?

1.1 最通俗的理解

想象你家里的遥控器

  • 客厅里只有1个电视遥控器
  • 全家人都在用这同一个遥控器
  • 不需要每个人自己买个遥控器

单例模式 = 一个类只能创建一个对象 + 所有人都用这个对象

1.2 为什么要用单例模式?

问题场景

java 复制代码
// 没有单例模式的问题
DatabaseManager db1 = new DatabaseManager();  // 连接数据库1
DatabaseManager db2 = new DatabaseManager();  // 连接数据库2
DatabaseManager db3 = new DatabaseManager();  // 连接数据库3
// 内存浪费!而且3个连接可能造成数据混乱

解决方案

java 复制代码
// 使用单例模式
DatabaseManager db1 = DatabaseManager.getInstance();  // 获取唯一实例
DatabaseManager db2 = DatabaseManager.getInstance();  // 还是同一个
// db1 和 db2 是同一个对象,节省内存,数据一致

二、单例模式的3个核心要素

java 复制代码
public class Singleton {
    // 1. 私有静态变量:保存唯一实例
    private static Singleton instance;
    
    // 2. 私有构造函数:防止外部 new
    private Singleton() {
        // 初始化代码
    }
    
    // 3. 公共静态方法:提供全局访问点
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

三、Java中的6种单例写法(从简单到完善)

写法1:饿汉式(最简单,最常用)

java 复制代码
public class DatabaseManager {
    // 类加载时就创建,线程安全
    private static final DatabaseManager instance = new DatabaseManager();
    
    private DatabaseManager() {
        // 初始化数据库连接
        System.out.println("连接数据库");
    }
    
    public static DatabaseManager getInstance() {
        return instance;
    }
    
    public void query(String sql) {
        System.out.println("执行查询:" + sql);
    }
}

// 使用
DatabaseManager db = DatabaseManager.getInstance();
db.query("SELECT * FROM users");

优点 :简单、线程安全
缺点:即使不用也会创建,浪费内存

如果写起来有点复杂,那就简化

java 复制代码
// 饿汉式(最简单!)
public class UserManager {
    // 就这一行!
    public static final UserManager INSTANCE = new UserManager();
    
    private UserManager() {}  // 私有构造
    
    public void doSomething() {}
}

// 使用
UserManager.INSTANCE.doSomething();

写法2:懒汉式(延迟加载)

java 复制代码
public class ConfigManager {
    private static ConfigManager instance;
    
    private ConfigManager() {
        System.out.println("加载配置文件");
    }
    
    public static ConfigManager getInstance() {
        if (instance == null) {  // 第一次调用时才创建
            instance = new ConfigManager();
        }
        return instance;
    }
}

// 使用
ConfigManager config = ConfigManager.getInstance();  // 这时才真正创建

优点 :需要时才创建,节省内存
缺点:多线程环境下可能创建多个实例(新手可以暂时忽略)

写法3:双重检查锁(最推荐,兼顾性能和线程安全)

java 复制代码
public class UserManager {
    // volatile 关键字:防止指令重排序
    private static volatile UserManager instance;
    
    private UserManager() {
        System.out.println("初始化用户管理器");
    }
    
    public static UserManager getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (UserManager.class) {  // 加锁
                if (instance == null) {  // 第二次检查
                    instance = new UserManager();
                }
            }
        }
        return instance;
    }
    
    private String currentUser;
    
    public void setCurrentUser(String name) {
        this.currentUser = name;
    }
    
    public String getCurrentUser() {
        return currentUser;
    }
}

这是Android中最推荐的写法!

写法4:静态内部类(优雅的写法)

java 复制代码
public class ImageLoader {
    // 静态内部类负责创建实例
    private static class Holder {
        private static final ImageLoader INSTANCE = new ImageLoader();
    }
    
    private ImageLoader() {
        System.out.println("初始化图片加载器");
    }
    
    public static ImageLoader getInstance() {
        return Holder.INSTANCE;
    }
    
    public void loadImage(String url) {
        System.out.println("加载图片:" + url);
    }
}

写法5:枚举(最安全,但Android中少用)

java 复制代码
public enum NetworkManager {
    INSTANCE;  // 唯一实例
    
    public void connect() {
        System.out.println("连接网络");
    }
}

// 使用
NetworkManager.INSTANCE.connect();

写法6:Kotlin的object(Android现代开发推荐)

kotlin 复制代码
// Kotlin 中一行代码搞定
object UserDataManager {
    var userName: String = ""
    var userAge: Int = 0
    
    fun saveUser(name: String, age: Int) {
        userName = name
        userAge = age
    }
}

// 使用
UserDataManager.saveUser("张三", 25)
println(UserDataManager.userName)

四、Android中的实际应用案例

案例1:全局用户信息管理

java 复制代码
public class UserSession {
    private static volatile UserSession instance;
    
    // 用户数据
    private String userId;
    private String userName;
    private boolean isLoggedIn;
    
    private UserSession() {}
    
    public static UserSession getInstance() {
        if (instance == null) {
            synchronized (UserSession.class) {
                if (instance == null) {
                    instance = new UserSession();
                }
            }
        }
        return instance;
    }
    
    // 登录
    public void login(String userId, String userName) {
        this.userId = userId;
        this.userName = userName;
        this.isLoggedIn = true;
    }
    
    // 退出
    public void logout() {
        this.userId = null;
        this.userName = null;
        this.isLoggedIn = false;
    }
    
    // 获取用户信息
    public String getUserName() {
        return userName;
    }
    
    public boolean isLoggedIn() {
        return isLoggedIn;
    }
}

// 在Activity中使用
public class LoginActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 登录成功后保存用户信息
        UserSession.getInstance().login("12345", "张三");
        
        // 跳转到主页
        startActivity(new Intent(this, MainActivity.class));
    }
}

// 在另一个Activity中获取
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 获取当前登录用户
        String userName = UserSession.getInstance().getUserName();
        TextView tvName = findViewById(R.id.tv_name);
        tvName.setText("欢迎:" + userName);
    }
}

案例2:网络请求管理器

java 复制代码
public class OkHttpManager {
    private static volatile OkHttpManager instance;
    private OkHttpClient okHttpClient;
    
    private OkHttpManager() {
        // 初始化 OkHttpClient
        okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build();
    }
    
    public static OkHttpManager getInstance() {
        if (instance == null) {
            synchronized (OkHttpManager.class) {
                if (instance == null) {
                    instance = new OkHttpManager();
                }
            }
        }
        return instance;
    }
    
    public void get(String url, Callback callback) {
        Request request = new Request.Builder()
            .url(url)
            .build();
        okHttpClient.newCall(request).enqueue(callback);
    }
}

// 使用
OkHttpManager.getInstance().get("https://api.example.com/data", new Callback() {
    @Override
    public void onResponse(Call call, Response response) {
        // 处理响应
    }
    
    @Override
    public void onFailure(Call call, IOException e) {
        // 处理错误
    }
});

案例3:SharedPreferences工具类

java 复制代码
public class SPManager {
    private static volatile SPManager instance;
    private SharedPreferences sp;
    private SharedPreferences.Editor editor;
    
    private SPManager(Context context) {
        // 使用ApplicationContext避免内存泄漏
        sp = context.getApplicationContext()
            .getSharedPreferences("my_app", Context.MODE_PRIVATE);
        editor = sp.edit();
    }
    
    public static SPManager getInstance(Context context) {
        if (instance == null) {
            synchronized (SPManager.class) {
                if (instance == null) {
                    instance = new SPManager(context);
                }
            }
        }
        return instance;
    }
    
    public void putString(String key, String value) {
        editor.putString(key, value).apply();
    }
    
    public String getString(String key, String defaultValue) {
        return sp.getString(key, defaultValue);
    }
    
    public void putBoolean(String key, boolean value) {
        editor.putBoolean(key, value).apply();
    }
    
    public boolean getBoolean(String key, boolean defaultValue) {
        return sp.getBoolean(key, defaultValue);
    }
}

// 使用
SPManager.getInstance(this).putString("username", "张三");
String name = SPManager.getInstance(this).getString("username", "");

五、常见错误和注意事项

❌ 错误1:忘记私有化构造函数

java 复制代码
public class WrongSingleton {
    // 错误:构造函数是public,别人可以new多个实例
    public WrongSingleton() {}
}

❌ 错误2:在Android中持有Activity引用

java 复制代码
public class BadSingleton {
    private Context context;  // 危险!
    
    public void setContext(Context context) {
        this.context = context;  // 可能持有Activity导致内存泄漏
    }
}

// ✅ 正确做法:使用ApplicationContext
public class GoodSingleton {
    private Context context;
    
    public void setContext(Context context) {
        // 使用ApplicationContext,不会泄漏Activity
        this.context = context.getApplicationContext();
    }
}

❌ 错误3:在单例中做耗时操作

java 复制代码
public class BadNetworkManager {
    public void loadData() {
        // 错误:在主线程做网络请求
        HttpURLConnection conn = ...;  // 会卡死UI
    }
}

// ✅ 正确做法:使用线程或协程
public class GoodNetworkManager {
    public void loadData() {
        new Thread(() -> {
            // 在子线程做耗时操作
            HttpURLConnection conn = ...;
        }).start();
    }
}

六、什么时候用单例?

✅ 适合用单例的场景

  • 数据库操作类(Room、SQLite)
  • 网络请求类(OkHttp、Retrofit)
  • SharedPreferences工具类
  • 图片加载器(Glide、Picasso)
  • 全局配置管理
  • 日志记录器
  • 线程池管理

❌ 不适合用单例的场景

  • 用户信息(如果用单例,所有用户都一样了)
  • Activity、Fragment(系统会管理生命周期)
  • 包含大量可变状态的对象
  • 需要被继承的类

七、总结和最佳实践

新手记住这3点就够了:

  1. 写法选择 :Android开发用双重检查锁静态内部类
  2. Context处理 :永远用getApplicationContext()
  3. 避免持有:不要在单例中持有Activity/View引用

最简单的模板(复制即用):

java 复制代码
public class YourManager {
    private static volatile YourManager instance;
    
    private YourManager() {
        // 初始化
    }
    
    public static YourManager getInstance() {
        if (instance == null) {
            synchronized (YourManager.class) {
                if (instance == null) {
                    instance = new YourManager();
                }
            }
        }
        return instance;
    }
    
    // 你的方法
    public void doSomething() {
        // ...
    }
}

单例不是万能的,不要滥用。只有真正需要"全局唯一"的时候才用单例模式!

什么时候使用单例模式

不是所有全局数据都应该用单例,我来帮你判断什么时候该用,什么时候不该用。

快速判断表

数据类型 是否用单例 推荐方案
用户登录信息 ✅ 适合 单例 + 持久化存储
App配置参数 ✅ 适合 单例
网络请求管理器 ✅ 适合 单例
数据库帮助类 ✅ 适合 单例
购物车数据 ⚠️ 看情况 单例 或 ViewModel
页面间传递的数据 ❌ 不适合 Intent / ViewModel
不同用户的数据 ❌ 不适合 普通对象
临时性数据 ❌ 不适合 局部变量

详细分析:何时用单例?

✅ 适合用单例的场景

1. 系统级服务(全局只有一个)
java 复制代码
// 适合:网络管理器
public class NetworkManager {
    private static NetworkManager instance;
    private OkHttpClient client;
    
    // 整个App只需要一个网络客户端
    // ✅ 适合用单例
}

// 适合:数据库管理器
public class DatabaseManager {
    private static DatabaseManager instance;
    private RoomDatabase db;
    
    // 整个App只需要一个数据库连接池
    // ✅ 适合用单例
}
2. 全局配置信息
java 复制代码
// 适合:App配置
public class AppConfig {
    private static AppConfig instance;
    
    private boolean isDarkMode;
    private String apiUrl;
    private int timeoutSeconds;
    
    // 配置全局只有一份,所有页面共用
    // ✅ 适合用单例
}
3. 用户会话数据(当前登录用户)
java 复制代码
// 适合:当前用户信息
public class UserSession {
    private static UserSession instance;
    
    private User currentUser;
    private String authToken;
    
    // 当前登录的用户信息,全局唯一
    // ✅ 适合用单例
}

❌ 不适合用单例的场景

1. 多个用户的数据
java 复制代码
// ❌ 错误:用户列表不应该用单例
public class UserList {
    private static UserList instance;  // 错误!
    private List<User> users;
}

// ✅ 正确:普通类,需要时创建
public class UserList {
    private List<User> users;
    // 不需要单例
}

// 使用
UserList friends = new UserList();     // 好友列表
UserList followers = new UserList();   // 粉丝列表
// 这是两个不同的对象,不应该用单例
2. 页面间临时传递的数据
java 复制代码
// ❌ 错误:用单例传递数据
public class TempDataHolder {
    private static TempDataHolder instance;
    private String tempData;  // 容易混乱
    
    public static TempDataHolder getInstance() {
        // ...
    }
}

// Activity A
TempDataHolder.getInstance().setTempData("some data");
startActivity(new Intent(this, ActivityB.class));

// Activity B
String data = TempDataHolder.getInstance().getTempData();
// 问题:如果Activity被系统杀死,数据丢失
// 问题:多个页面共用会互相覆盖

// ✅ 正确:用Intent传递
Intent intent = new Intent(this, ActivityB.class);
intent.putExtra("data", "some data");
startActivity(intent);
3. 列表中的多个项目
java 复制代码
// ❌ 错误:购物车商品条目
public class CartItem {
    private static CartItem instance;  // 错误!
    private String productName;
    private int quantity;
}
// 购物车有多个商品,不应该用单例

// ✅ 正确:普通类
public class CartItem {
    private String productName;
    private int quantity;
    // 每个商品独立创建
}

特殊情况:需要根据生命周期判断

场景:购物车数据

java 复制代码
// 方案1:如果购物车需要全局访问,可以用单例
public class ShoppingCart {
    private static ShoppingCart instance;
    private List<CartItem> items;
    
    public void addItem(CartItem item) {
        items.add(item);
    }
    // ✅ 可以用单例,但要注意数据持久化
}

// 方案2:更好的方案 - ViewModel(推荐)
public class ShoppingCartViewModel extends ViewModel {
    private MutableLiveData<List<CartItem>> items = new MutableLiveData<>();
    
    public void addItem(CartItem item) {
        // 处理添加逻辑
    }
}
// ✅ 更推荐:生命周期感知,屏幕旋转不丢失

Android特有的最佳实践

1. Application类作为单例替代品

java 复制代码
// 方式1:自定义Application
public class MyApplication extends Application {
    private UserData userData;
    
    public UserData getUserData() {
        return userData;
    }
    
    public void setUserData(UserData data) {
        this.userData = data;
    }
}

// 使用
MyApplication app = (MyApplication) getApplicationContext();
app.setUserData(userData);
// ✅ 优点:生命周期跟随App,永远不会为null

2. ViewModel + LiveData(推荐用于UI相关数据)

java 复制代码
// 推荐:用于Activity/Fragment间的数据共享
public class SharedViewModel extends ViewModel {
    private MutableLiveData<UserData> userData = new MutableLiveData();
    
    public void setUserData(UserData data) {
        userData.setValue(data);
    }
    
    public LiveData<UserData> getUserData() {
        return userData;
    }
}

// Activity A
SharedViewModel viewModel = new ViewModelProvider(this).get(SharedViewModel.class);
viewModel.setUserData(userData);

// Activity B(同一个Activity的Fragment)
SharedViewModel viewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
viewModel.getUserData().observe(this, data -> {
    // 自动接收更新
});
// ✅ 优点:生命周期安全,屏幕旋转不丢失

3. 依赖注入(现代Android推荐)

kotlin 复制代码
// 使用Hilt
@Singleton
class UserManager @Inject constructor() {
    // Hilt保证全局单例
}

@HiltAndroidApp
class MyApplication : Application()

决策流程图

复制代码
需要全局管理的数据
    │
    ├─ 是系统级服务(网络、数据库)? ──YES──→ 使用单例 ✅
    │
    ├─ 是App全局配置? ──YES──→ 使用单例 ✅
    │
    ├─ 是当前登录用户信息? ──YES──→ 使用单例 ✅
    │
    ├─ 是多个独立对象(用户列表、商品列表)? ──YES──→ 不要用单例 ❌
    │
    ├─ 是页面间临时数据? ──YES──→ 用Intent/Arguments ❌
    │
    ├─ 与UI相关,需要感知生命周期? ──YES──→ 用ViewModel ✅
    │
    └─ 都不符合 ──→ 用普通类或依赖注入

实战示例:混合使用

java 复制代码
// 1. 用户会话 - 用单例
public class UserSession {
    private static UserSession instance;
    private User currentUser;  // 当前登录用户
    
    public static UserSession getInstance() { ... }
}

// 2. 消息列表 - 不用单例,每个会话独立
public class MessageList {
    private List<Message> messages;
    private String conversationId;  // 每个对话有自己的消息列表
}

// 3. 应用配置 - 用单例
public class SettingsManager {
    private static SettingsManager instance;
    private boolean isDarkMode;
    private String language;
}

// 4. 页面间共享数据 - 用ViewModel
public class SharedViewModel extends ViewModel {
    private MutableLiveData<OrderData> orderData;
}

总结建议

用单例当:

  • ✅ 整个App只需要一个实例(网络、数据库)
  • ✅ 配置信息全局唯一
  • ✅ 当前登录用户信息

不用单例当:

  • ❌ 会有多个独立对象(列表数据)
  • ❌ 生命周期短暂(临时数据)
  • ❌ 不同场景不同数据

更好的替代方案:

  1. ViewModel:UI相关的共享数据
  2. Intent/Bundle:页面间简单数据传递
  3. Application类:App级别的全局变量
  4. 依赖注入:大型项目的推荐方式

单例不是全局数据的唯一解决方案,选择合适的方式更重要!

相关推荐
还在忙碌的吴小二1 天前
Harness 最佳实践:Java Spring Boot 项目落地 OpenSpec + Claude Code
java·开发语言·spring boot·后端·spring
风吹迎面入袖凉1 天前
【Redis】Redis的五种核心数据类型详解
java·redis
夕除1 天前
javaweb--02
java·tomcat
ailvyuanj1 天前
2026年Java AI开发实战:Spring AI完全指南
java
张np1 天前
java进阶-Dubbo
java·dubbo
汽车仪器仪表相关领域1 天前
NHFID-1000型非甲烷总烃分析仪:技术破局,重构固定污染源监测新体验
java·大数据·网络·人工智能·单元测试·可用性测试·安全性测试
一叶飘零_sweeeet1 天前
深入理解 AQS:从架构到实现,解锁 Java 并发编程的核心密钥
java·aqs
恋猫de小郭1 天前
Android 上为什么主题字体对 Flutter 不生效,对 Compose 生效?Flutter 中文字体问题修复
android·前端·flutter
三少爷的鞋1 天前
不要让调用方承担你本该承担的复杂度 —— Android Data 层设计原则
android
李李李勃谦1 天前
Flutter 框架跨平台鸿蒙开发 - 创意灵感收集
android·flutter·harmonyos