单例模式介绍
一、什么是单例模式?
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点就够了:
- 写法选择 :Android开发用双重检查锁 或静态内部类
- Context处理 :永远用
getApplicationContext() - 避免持有:不要在单例中持有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只需要一个实例(网络、数据库)
- ✅ 配置信息全局唯一
- ✅ 当前登录用户信息
不用单例当:
- ❌ 会有多个独立对象(列表数据)
- ❌ 生命周期短暂(临时数据)
- ❌ 不同场景不同数据
更好的替代方案:
- ViewModel:UI相关的共享数据
- Intent/Bundle:页面间简单数据传递
- Application类:App级别的全局变量
- 依赖注入:大型项目的推荐方式
单例不是全局数据的唯一解决方案,选择合适的方式更重要!