using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SingleInstanceDemo
{
//断子绝孙的类
public sealed class MyCacheItem<T> : IDisposable where T : class
{
private volatile T _value;
//只能对类和结构的字段,使用这个关键字
//将 volatile 修饰符添加到 _shouldStop 的声明后,将始终获得相同的结果
//https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/volatile?f1url=%3FappId%3DDev16IDEF1%26l%3DZH-CN%26k%3Dk(volatile_CSharpKeyword);k(TargetFrameworkMoniker-.NETFramework,Version%253Dv4.5);k(DevLang-csharp)%26rd%3Dtrue
//初始化引用指定对象的 WeakReference<T> 类的新实例。
//反正声明一种弱引用类型
private WeakReference<T> _weakObject;
//只读
private readonly object _syncLock = new object();
private long _expiration;
private readonly bool _useWeakReference;
/// <summary>
/// 构造方法
/// </summary>
/// <param name="value">缓存结果</param>
/// <param name="expiration">缓存的过期时间,如果访问Value属性时【超过】过期时间,则返回结果类型的默认值</param>
/// <param name="useWeakReference">是否允许使用弱引用来缓存对象</param>
public MyCacheItem(T value, DateTime expiration, bool useWeakReference = true)
{
_useWeakReference = useWeakReference;
SetValue(value, expiration);
}
private void SetValue(T value, DateTime expiration)
{
//设置值,明显多个线程也要都是使用这个缓存内容,否则出现脏数据bug
lock (_syncLock)
{
//如果设置的值是null,那么对应的弱引用对象,和值都是null
if (value == null)
{
_value = null;
_weakObject = null;
//就是一个赋值操作,即让_expiration = DateTime.MinValue.Ticks;
//这里是线程安全的,即多个线程来操作,也能保证原子操作,都是同一个值,
Interlocked.Exchange(ref _expiration, DateTime.MinValue.Ticks);
}
//设置相关的值,和过期时间
else
{
//先判断是否采用弱引用,符合条件的情况指定过,
if (UseWeakReference(value, expiration))
{
//是弱引用,那么真实的值_value = null,
//采用弱引用方式,那么使用之前先判断,
// 防止开发人员缓存大对象,长时间运行会导致OOM,所以这里使用【弱引用】
_value = null;
_weakObject = new WeakReference<T>(value);
}
else
{
_value = value;
_weakObject = null;
}
Interlocked.Exchange(ref _expiration, expiration.Ticks);
}
}
}
private bool UseWeakReference(T value, DateTime expiration)
{
if (_useWeakReference == false)
return false;
// 永不过期的对象,不用弱引用保持
if (expiration == DateTime.MaxValue)
return false;
// 【小字符串】,不用弱引用保持。 这里的【大/小】和GC的85K大对象不是一回事。
if (typeof(T) == typeof(string) && ((string)(object)value).Length < 1024)
return false;
return true;
}
/// <summary>
/// 修改缓存值
/// </summary>
/// <param name="value">缓存结果</param>
/// <param name="expiration">过期时间</param>
public void Set(T value, DateTime expiration)
{
SetValue(value, expiration);
}
/// <summary>
/// 从缓存中获取结果
/// </summary>
/// <returns>已缓存的结果。如果缓存中不存在或者超过了过期时间,则返回结果类型的默认值</returns>
public T Get()
{
//设置值,明显多个线程也要都是使用这个缓存内容,否则出现脏数据bug
lock (_syncLock)
{
//存在这个强类型,
if (_value != null)
{
// 过期那么清除缓存自身这个对象,可以释放掉
if (IsExpired())
{
Dispose(); // 清除缓存引用(延迟清理)
return default(T);
}
//没有过期那么直接返回这个值,
else
{
return _value;
}
}
//如果弱类型不为空,那么前面的value可能为空,
if (_weakObject != null)
{
//过期都是返回默认值null
if (IsExpired())
{
Dispose(); // 清除缓存引用(延迟清理)
return default(T);
}
else
{
T data = default(T);
//输出弱引用的结果,
_weakObject.TryGetTarget(out data);
return data;
}
}
}
return default(T);
}
//使用的时候,判断当前时间和过期时间关系,大于过期时间,即已经过期
internal bool IsExpired()
{
return DateTime.Now.Ticks > Interlocked.Read(ref _expiration);
}
//
//public MyCacheItem()
//{
//}
public void Dispose()
{
//执行释放过程,同样保证多线程也是同步
//不同的实例,即不同进程不用考虑,因为各自维护的静态变量不一致,
lock (_syncLock)
{
//不为空,但是已经过期,
if (_value != null)
{
//可释放的对象,直接调用释放,
IDisposable disposable = _value as IDisposable;
disposable?.Dispose();
// 释放引用
_value = null;
}
//如果弱类型,存在
if (_weakObject != null)
{
T weakValue = default(T);
_weakObject.TryGetTarget(out weakValue);
//从弱引用中拿到当前缓存对象,
//释放当前缓存值,
if (weakValue != null)
{
IDisposable disposable = weakValue as IDisposable;
disposable?.Dispose();
}
// 释放弱引用自身引用,和弱引用的对象的引用
_weakObject.SetTarget(null);
_weakObject = null;
}
}
}
}
}