题目:设计一个日志管理器(LogManager)的单例模式。要求在整个应用程序中只能存在一个日志管理器实例,通过该实例可以记录日志信息并输出到控制台。
要求:
LogManager类只能有一个实例,并提供全局访问点来获取该实例。
LogManager类在第一次被访问时进行初始化,并记录当前时间作为日志的开始时间。
LogManager类应该提供一个公有的方法来记录日志信息。每条日志信息包含日志时间和日志内容。
LogManager类应该提供一个公有的方法来输出所有日志信息到控制台。
LogManager类的实现应该是线程安全的,也就是说多个线程同时访问该类时,不会出现竞争条件。
LogManager类的代码应该具有良好的可读性和可维护性。
提示:
可以使用静态变量和静态构造函数来实现单例模式。
可以使用线程锁(Monitor)来实现线程安全性。
可以使用队列(Queue)来存储日志信息,在输出时按照先进先出的顺序输出。
参照コード:
using System.Collections.Concurrent;
namespace 单例
{
//sealed全局只有一个实例 防止类被继承
internal sealed class LogManager
{
//volatile 修饰后的变量 如果修改 可以被其他线程知晓
private static volatile LogManager instance;
private static object syncRoot = new object();
private ConcurrentQueue<string> logQueue;
private DateTime startTime;
private LogManager()
{
logQueue= new ConcurrentQueue<string>();
startTime= DateTime.Now;
}
public static LogManager Instance {
get {
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new LogManager();
}
}
}
return instance;
}
}
public void Log(string message)
{
string log = $"{
DateTime.UtcNow}:{
message}";
logQueue.Enqueue(log);
}
public void PrintLogs()
{
while (logQueue.TryDequeue(out string log))
{
Console.WriteLine(log);
System.Threading.Thread.Sleep(200);
}
}
}
}
sealed
このキーワードは、クラスを変更して、クラスが継承できないことを示すために使用されます。アプリケーション全体でクラスのインスタンスを 1 つだけ存在させたい場合は、シングルトン パターンを使用してシングルトン クラスをマークし、sealed
他のクラスがそのクラスを継承して複数のインスタンスを作成するのを防ぐことができます。
volatile
キーワードはフィールドまたは変数を変更するために使用され、フィールドまたは変数がマルチスレッド環境で表示され、いつでも変更できることを示します。マルチスレッド プログラムでは、各スレッドには独自のプライベート スレッド キャッシュがあります。1 つのスレッドがフィールドまたは変数を変更するときに、キーワードで変更されていない場合、他のスレッドはまだ独自のスレッド キャッシュを使用しているため、変更がすぐに認識されない可能性がありますvolatile
。 。キーワードを使用してvolatile
フィールドまたは変数を変更すると、フィールドまたは変数の読み取りおよび書き込み操作がメイン メモリ内で直接実行され、可視性と一貫性が保証されます。
上記のコード例では次のようになります。
sealed
LogManager
キーワードは、他のクラスがクラスを継承しないようにクラスを変更するために使用されます。
volatile
このキーワードはフィールドを変更して、instance
マルチスレッド環境でinstance
メイン メモリからの読み取りと書き込みが確実に実行されるようにするために使用され、競合状態によって引き起こされる問題を回避します。
マルチスレッド プログラミングでキーワードを使用しても、volatile
特に複合操作やより複雑な同期が必要な場合には、スレッドの安全性を確保するには必ずしも十分ではないことに注意してください。lock
このような場合、通常はステートメントやMonitor
クラスMutex
などのより強力な同期ツールを使用する必要があります。
シングルトンモードの実装では、インスタンスが一つだけ作成されるようにするため、複数のスレッドがif (instance == null)
同時に通過の判定条件に遭遇する場合があります。2 つのスレッドがほぼ同時に条件を通過すると、両方ともミューテックスの(lock)
コード ブロックに入り、複数のインスタンスが作成される可能性があります。
この問題を解決するには、二重チェックのロック機構を使用します。まず、インスタンスを作成しないと、両方のスレッドが最初のif (instance == null)
判定条件を通過します。次に、(lock)
ミューテックス ロックを取得して、1 つのスレッドのみが実行を継続できるようにします。その後、ロックを取得したスレッドが mutex(lock)
のコード ブロックに入ると、前のスレッドが mutex に入る前に別のスレッドがインスタンスを作成した可能性があるため、インスタンスが空かどうかを再度チェックします。2 回目のチェックでインスタンスがまだ空の場合は、そのスレッドがインスタンスの作成を担当し、インスタンスが 1 つだけ作成されてinstance
フィールドに割り当てられるようにします。
これの目的は、不必要なロック競合を回避し、パフォーマンスを向上させることです。最初のif (instance == null)
チェックは、ミューテックス ロックを待たずにすぐに戻ることができます。ミューテックスはインスタンスを作成する必要がある場合にのみ取得され、1 つのスレッドだけがインスタンスを作成できるようになります。2 番目のif (instance == null)
チェックは、別のスレッドがミューテックスを取得してインスタンスを作成する前に、現在のスレッドがブロックされ、ミューテックスの終了を待機するのを防ぐためです。この時点では、他のスレッドによって作成されている可能性があり、複数のインスタンスの作成を回避しますinstance
。質問。
null
要約すると、二重チェックされたロック機構は、値を 2 回検証してインスタンスが 1 つだけ作成されるようにし、ロック競合をできる限り減らすことで、パフォーマンスとスレッドの安全性の間のトレードオフを実現します。
注:アクセス修飾子
を削除しますinternal
。他の名前空間のクラスにアクセスできるようにする場合はLogManager
、アクセス修飾子を に変更しますpublic
。
logQueue
および をインスタンス フィールドとして変更するstartTime
: 静的コンテキストで不必要な同期が発生するのを避けるために、これら 2 つのフィールドをstatic
インスタンス フィールドに変更します。
ConcurrentQueue<T>
代わりにQueue<T>:ConcurrentQueue<T>
スレッドセーフ キューを使用すると、手動ロックを回避してキューに並列アクセスできます。
マルチスレッド環境でタイムスタンプをより正確に取得するには、DateTime.UtcNow
代わりに使用します。DateTime.Now:
UtcNow