单例模式 Singleton

  • 保证一个类只有一个实例的实现方法
  • 给其他类提供一个全局的访问点。
  • 由自己创建自己的唯一实例

实现

  • 实现方法分为饿汉式(线程安全)、懒汉式(线程不安全)、懒汉式(lock+双重验证、线程安全)、延迟加载(Lazy、线程安全)

1.饿汉式

这种方式比较常用,但容易产生垃圾对象.这时候初始化 instance 显然没有达到 lazy loading 的效果。

优点:没有加锁,执行效率会提高。

缺点:类加载时就初始化,浪费内存。

public class EagerSingleton
{
private EagerSingleton() { }
private static readonly EagerSingleton Instance = new EagerSingleton(); public static EagerSingleton GetInstance()
{
return Instance;
}
}

2.最简单的实现:懒汉式(线程不安全)

namespace Singleton
{
public class Singleton
{
private static Singleton _uniqueInstance; private Singleton()
{
} public static Singleton GetInstance()
{
if (_uniqueInstance is null)
{
_uniqueInstance = new Singleton();
} return _uniqueInstance;
}
}
}

定义了一个静态方法,作为全局访问点,在单线程下是正常的,在多线程同时运行GetInstance,得到的_uniqueInstance都是null,此时就会创建多个 定义了一个静态方法,作为全局访问点,在单线程下是正常的,在多线程同时运行GetInstance,得到的_uniqueInstance都是null,此时就会创建多个的实例。

多线程访问得到hash code是不一样的。

static void Main(string[] args)
{ Task.Run(() =>
{
Singleton singleton = Singleton.GetInstance();
Console.WriteLine(singleton.GetHashCode());
}); Task.Run(() =>
{
Singleton singleton = Singleton.GetInstance();
Console.WriteLine(singleton.GetHashCode());
}); Console.WriteLine("over!");
}

over还提前输出。

over!
4032828
6044116
static void Main(string[] args)
{
Singleton singleton1 = Singleton.GetInstance();
Console.WriteLine(singleton1.GetHashCode()); Singleton singleton2 = Singleton.GetInstance();
Console.WriteLine(singleton2.GetHashCode()); Console.WriteLine("over!");
}

输出

58225482
58225482
over!

3.懒汉式(lock+双重验证、线程安全)

lock关键字

MSDN介绍

lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。 ThreadInterruptedException 引发,如果 Interrupt 中断等待输入 lock 语句的线程。
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。 常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
如果实例可以被公共访问,将出现 lock (this) 问题。
如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。
由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock("myLock") 问题。
最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。
在 lock 语句的正文不能使用 等待 关键字。

最常使用的锁是如下格式的代码段:

private static object objlock = new object();
lock (objlock )
{
//要执行的代码逻辑
}

使用lock关键字解决多线程问题

public static LockSingleton GetInstance()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
lock (Locker)
{
// 如果类的实例不存在则创建,否则直接返回
if (_uniqueInstance == null)
{
_uniqueInstance = new LockSingleton();
}
}
return _uniqueInstance;
}

在lock之前判断是否实例

上面的代码还可以优化,通过判断对象是否为null,如果不是null,则直接返回,否则先锁,然后再生成实例,保证不同线程访问得到的是一个实例

public static LockSingleton GetInstance()
{
// 双重锁定只需要一句判断就可以了
if (_uniqueInstance == null)
{
lock (Locker)
{
if (_uniqueInstance == null)
{
_uniqueInstance = new LockSingleton();
}
}
}
return _uniqueInstance;
}

3.使用lock

Task.Run(() =>
{
LockSingleton lockSingleton = LockSingleton.GetInstance();
Console.WriteLine(lockSingleton.GetHashCode()); }); Task.Run(() =>
{
LockSingleton lockSingleton = LockSingleton.GetInstance();
Console.WriteLine(lockSingleton.GetHashCode());
});

输出结果

over!
6044116
6044116

延迟加载(Lazy)

public class LazySingleton
{
private static readonly Lazy<LazySingleton> SingletonLazy = new Lazy<LazySingleton>(() => new LazySingleton()); /// <summary>
/// 私有构造函数
/// </summary>
private LazySingleton()
{
Console.WriteLine("我被创建了.Lazy");
} /// <summary>
/// 获取实例
/// </summary>
/// <returns></returns>
public static LazySingleton GetInstance()
{
return SingletonLazy.Value;
}
}

总结

单例主要分为如下几种方式,在实际使用过程中:建议采用延迟加载(Lazy)

饿汉式 懒汉式 懒汉式+lock锁+双重判断 延迟加载(Lazy)
线程安全 线程不安全 线程安全 线程安全
不是延迟加载(会浪费内存) 会延迟加载 会延迟加载 会延迟加载
没有加锁 没有加锁 加锁 加锁

最新文章

  1. 20145212&amp;20145204信息安全系统实验四报告
  2. C++构造函数与虚表覆盖
  3. 使用timer控件控制进度条
  4. 关于EditText的OnClickListener失效的解决办法
  5. jquery ajax 实例
  6. [Hibernate] - Criteria Select
  7. UMeng 友盟的用户数,启动数 等
  8. xp 中的IIS安装成功之后,访问网页显示没有权限访问解决方法
  9. java reflection总结
  10. 周根项《一分钟速算》全集播放&amp;amp;下载地址
  11. IBATIS动态SQL(1)
  12. Java 获取年 月 日 时 分 秒
  13. java 环境的安装、设置免密登陆、进行hadoop安装、关闭防火墙
  14. [c/c++] programming之路(8)、汇编、求模、自增自减
  15. 给ThinkPad E470C 换个高分屏(1080P)
  16. Structs复习 Action
  17. DOS在这里
  18. OpenERP7.0安装后提示&ldquo;not supported&quot; ,如何去掉此提示
  19. Golang之实现一个负载均衡算法(随机,轮询)
  20. 在eclipse中使用Maven3(笔记二)

热门文章

  1. 关于WLS2中Ubuntu启用SSH远程登录功能,基于Xshell登录,支持Root
  2. .Net Core 常用开发工具(IDE和运行时、Visual Studio插件、Visual Studio Code插件)
  3. 27、myslq更改为不自动提交
  4. 揭开Docker的面纱
  5. 工作3年,还不会写单元测试?新技能get!
  6. Docker安装rabbitMQ主从
  7. Java:Java多线程实现性能测试
  8. Spring:Spring嵌套事务方式
  9. Java实验项目三——采用面向对象的方式设计线性表
  10. linux学习之路第九天(任务调度)