Heim  >  Artikel  >  Java  >  Singleton-Muster und Singleton in Java

Singleton-Muster und Singleton in Java

angryTom
angryTomnach vorne
2019-11-29 13:46:452231Durchsuche

Vorwort

Dieser Artikel stammt von meinem offiziellen Account. Wenn Sie ihn noch nicht gesehen haben, können Sie ihn sich noch einmal ansehen Einige Inhalte wurden heute wie folgt erklärt:

1. Was ist das Singleton-Muster? 🎜>

【Singleton-Muster], englischer Name: Singleton-Muster, dieses Muster ist sehr einfach, ein Typ erfordert nur eine Instanz, es ist ein häufig verwendetes Software-Designmuster, das zum Erstellungstyp gehört. Eine über die Singleton-Modus-Methode erstellte Klasse hat im aktuellen Prozess nur eine Instanz (bei Bedarf kann es sich auch um einen Singleton in einem Thread handeln, z. B.: Im Thread-Kontext wird nur dieselbe Instanz verwendet).

(Empfohlenes Video:

Java-Video-Tutorial) 1 Eine Instanzklasse kann nur eine Instanz haben.

2. Die Singleton-Klasse muss ihre eigene eindeutige Instanz erstellen.

3. Die Singleton-Klasse muss diese Instanz allen anderen Objekten bereitstellen.

Dann wissen wir wahrscheinlich, dass es während unseres gesamten Projektzyklus nur eine Instanz geben wird Instanz Produkte erneut.

Oben wurde das Designmuster eines Substantivs [Erstellungstyp] erwähnt. Was ist also das Designmuster des Schöpfungstyps?

Erstellungsmodus: Verantwortlich für die Objekterstellung. Wir verwenden diesen Modus, um die benötigten Objektinstanzen zu erstellen.

Neben der Erstellung gibt es zwei weitere Arten von Mustern:

Strukturelles (strukturelles) Muster: Behandelt die Kombination von Klassen und Objekten

Verhaltensmodus: Die Verantwortlichkeiten bei der Interaktion zwischen Klassen und Objekten sind in zwei Designmodi unterteilt:

Wir werden später darüber sprechen, aber ich werde sie hier vorerst nicht auflisten.

Konzentrieren wir uns auf die Analyse, wie eine Objektinstanz im Singleton-Modus beginnend bei 0 erstellt wird.

2. So erstellen Sie ein Singleton-Muster

Es gibt viele Möglichkeiten, das Singleton-Muster zu implementieren: Vom „Lazy-Man-Stil“ bis zum „Hungry-Man-Stil“. ", und schließlich ""Double Check Lock"-Modus, Hier erklären wir Ihnen langsam Schritt für Schritt, wie Sie einen Singleton erstellen.

1. Normale logische Reihenfolge des Denkens Da wir eine einzelne Instanz erstellen möchten, müssen wir zunächst lernen, wie man eine Instanz erstellt Beispiel, wie z. B. Sagen Sie etwa Folgendes:

/// <summary>
/// 定义一个天气类
/// </summary>
public class WeatherForecast
{
    public WeatherForecast()
    {
        Date = DateTime.Now;
    }
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string Summary { get; set; }
}


 [HttpGet]
 public WeatherForecast Get()
 {
     // 实例化一个对象实例
     WeatherForecast weather = new WeatherForecast();
     return weather;
 }

Bei jedem Besuch ändert sich die Zeit, sodass unsere Instanzen ständig erstellt und geändert werden:


Ich glaube, jeder kann sehen, was dieser Code bedeutet. Lassen Sie uns gleich nach unten gehen. Wir wissen, dass der Hauptzweck des Singleton-Modus ist:

Es muss sichergestellt werden, dass diese Instanz während des gesamten Betriebszyklus des Systems eindeutig ist, um sicherzustellen, dass in der Mitte keine Probleme auftreten.

Nun, lasst uns besser werden und besser werden. Haben wir nicht gesagt, dass wir der Einzige sein wollen? Ich kann einfach direkt zurückgeben:

 /// <summary>
 /// 定义一个天气类
 /// </summary>
 public class WeatherForecast
 {
     // 定义一个静态变量来保存类的唯一实例
     private static WeatherForecast uniqueInstance;

     // 定义私有构造函数,使外界不能创建该类实例
     private WeatherForecast()
     {
         Date = DateTime.Now;
     }
     /// <summary>
     /// 静态方法,来返回唯一实例
     /// 如果存在,则返回
     /// </summary>
     /// <returns></returns>
     public static WeatherForecast GetInstance()
     {
         // 如果类的实例不存在则创建,否则直接返回
         // 其实严格意义上来说,这个不属于【单例】
         if (uniqueInstance == null)
         {
             uniqueInstance = new WeatherForecast();
         }
         return uniqueInstance;
     }
     public DateTime Date { get; set; }public int TemperatureC { get; set; }
     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
     public string Summary { get; set; }
 }
Dann ändern wir die aufrufende Methode, da unser Standardkonstruktor privatisiert wurde und keine Instanzen mehr erstellt werden dürfen, also rufen wir ihn direkt auf:

[HttpGet]
 public WeatherForecast Get()
 {
     // 实例化一个对象实例
     WeatherForecast weather = WeatherForecast.GetInstance();
     return weather;
 }

Schauen wir uns abschließend den Effekt an:


Zu diesem Zeitpunkt können wir sehen, dass sich die Zeit nicht geändert hat, was bedeutet, dass unsere Instanz einzigartig ist , du bist fertig! Bist du nicht sehr glücklich!

Aber keine Sorge, hier kommt das Problem. Wir sind derzeit Single-Threaded, also gibt es nur einen. Was ist, wenn mehrere Threads gleichzeitig darauf zugreifen? Wird es normal sein?


Hier machen wir einen Test, indem wir Multithreading verwenden, um Folgendes aufzurufen:

[HttpGet]
 public WeatherForecast Get()
 {
     // 实例化一个对象实例
     //WeatherForecast weather = WeatherForecast.GetInstance();

     // 多线程去调用
     for (int i = 0; i < 3; i++)
     {
         var th = new Thread(
         new ParameterizedThreadStart((state) =>
         {
             WeatherForecast.GetInstance();
         })
         );
         th.Start(i);
     }
     return null;
 }

Dann wollen wir sehen, was der Effekt ist, der unserer Meinung nach sein sollte Wenn man den Konstruktor durchgeht, ist es eigentlich nicht:

3个线程在第一次访问GetInstance方法时,同时判断(uniqueInstance ==null)这个条件时都返回真,然后都去创建了实例,这个肯定是不对的。那怎么办呢,只要让GetInstance方法只运行一个线程运行就好了,我们可以加一个锁来控制他,代码如下:

public class WeatherForecast
{
    // 定义一个静态变量来保存类的唯一实例
    private static WeatherForecast uniqueInstance;
    // 定义一个锁,防止多线程
    private static readonly object locker = new object();

    // 定义私有构造函数,使外界不能创建该类实例
    private WeatherForecast()
    {
        Date = DateTime.Now;
    }
    /// <summary>
    /// 静态方法,来返回唯一实例
    /// 如果存在,则返回
    /// </summary>
    /// <returns></returns>
    public static WeatherForecast GetInstance()
    {
        // 当第一个线程执行的时候,会对locker对象 "加锁",
        // 当其他线程执行的时候,会等待 locker 执行完解锁
        lock (locker)
        {
            // 如果类的实例不存在则创建,否则直接返回
            if (uniqueInstance == null)
            {
                uniqueInstance = new WeatherForecast();
            }
        }

        return uniqueInstance;
    }
    public DateTime Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string Summary { get; set; }
}

这个时候,我们再并发测试,发现已经都一样了,这样就达到了我们想要的效果,但是这样真的是最完美的么,其实不是的,因为我们加锁,只是第一次判断是否为空,如果创建好了以后,以后就不用去管这个 lock 锁了,我们只关心的是 uniqueInstance 是否为空,那我们再完善一下:

/// <summary>
/// 定义一个天气类
/// </summary>
public class WeatherForecast
{
    // 定义一个静态变量来保存类的唯一实例
    private static WeatherForecast uniqueInstance;
    // 定义一个锁,防止多线程
    private static readonly object locker = new object();

    // 定义私有构造函数,使外界不能创建该类实例
    private WeatherForecast()
    {
        Date = DateTime.Now;
    }
    /// <summary>
    /// 静态方法,来返回唯一实例
    /// 如果存在,则返回
    /// </summary>
    /// <returns></returns>
    public static WeatherForecast GetInstance()
    {
        // 当第一个线程执行的时候,会对locker对象 "加锁",
        // 当其他线程执行的时候,会等待 locker 执行完解锁
        if (uniqueInstance == null)
        {
            lock (locker)
            {
                // 如果类的实例不存在则创建,否则直接返回
                if (uniqueInstance == null)
                {
                    uniqueInstance = new WeatherForecast();
                }
            }
        }

        return uniqueInstance;
    }
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string Summary { get; set; }
}

这样才最终的完美实现我们的单例模式!搞定。

2、幽灵事件:指令重排

当然,如果你看完了上边的那四步已经可以出师了,平时我们就是这么使用的,也是这么想的,但是真的就是万无一失么,有一个 JAVA 的朋友提出了这个问题,C# 中我没有听说过,是我孤陋寡闻了么:

单例模式的幽灵事件,时令重排会偶尔导致单例模式失效。

是不是听起来感觉很高大上,而不知所云,没关系,咱们平时用不到,但是可以了解了解:

为何要指令重排?       

指令重排是指的 volatile,现在的CPU一般采用流水线来执行指令。一个指令的执行被分成:取指、译码、访存、执行、写回、等若干个阶段。然后,多条指令可以同时存在于流水线中,同时被执行。
指令流水线并不是串行的,并不会因为一个耗时很长的指令在“执行”阶段呆很长时间,而导致后续的指令都卡在“执行”之前的阶段上。
相反,流水线是并行的,多个指令可以同时处于同一个阶段,只要CPU内部相应的处理部件未被占满即可。比如说CPU有一个加法器和一个除法器,那么一条加法指令和一条除法指令就可能同时处于“执行”阶段, 而两条加法指令在“执行”阶段就只能串行工作。
相比于串行+阻塞的方式,流水线像这样并行的工作,效率是非常高的。

然而,这样一来,乱序可能就产生了。比如一条加法指令原本出现在一条除法指令的后面,但是由于除法的执行时间很长,在它执行完之前,加法可能先执行完了。再比如两条访存指令,可能由于第二条指令命中了cache而导致它先于第一条指令完成。
一般情况下,指令乱序并不是CPU在执行指令之前刻意去调整顺序。CPU总是顺序的去内存里面取指令,然后将其顺序的放入指令流水线。但是指令执行时的各种条件,指令与指令之间的相互影响,可能导致顺序放入流水线的指令,最终乱序执行完成。这就是所谓的“顺序流入,乱序流出”。

 这个是从网上摘录的,大概意思看看就行,理解双检锁失效原因有两个重点

1、编译器的写操作重排问题.

例 : B b = new B();

上面这一句并不是原子性的操作,一部分是new一个B对象,一部分是将new出来的对象赋值给b.

直觉来说我们可能认为是先构造对象再赋值.但是很遗憾,这个顺序并不是固定的.再编译器的重排作用下,可能会出现先赋值再构造对象的情况.

2、结合上下文,结合使用情景.

理解了1中的写操作重排以后,我卡住了一下.因为我真不知道这种重排到底会带来什么影响.实际上是因为我看代码看的不够仔细,没有意识到使用场景.双检锁的一种常见使用场景就是在单例模式下初始化一个单例并返回,然后调用初始化方法的方法体内使用初始化完成的单例对象.

三、Singleton = 单例 ?

 上边我们说了很多,也介绍了很多单例的原理和步骤,那这里问题来了,我们在学习依赖注入的时候,用到的 Singleton 的单例注入,是不是和上边说的一回事儿呢,这里咱们直接多多线程测试一下就行:

/// <summary>
/// 定义一个心情类
/// </summary>
public class Feeling
{
    public Feeling()
    {
        Date = DateTime.Now;
    }
    public DateTime Date { get; set; }
}


 // 单例注册到容器内
 services.AddSingleton<Feeling>();

这里重点表扬下评论区的@我是你帅哥 小伙伴,及时的发现了我文章的漏洞,笔芯!

紧接着我们就控制器注入服务,然后多线程测试:

 private readonly ILogger<WeatherForecastController> _logger;
 private readonly Feeling _feeling;

 public WeatherForecastController(ILogger<WeatherForecastController> logger, Feeling feeling)
 {
     _logger = logger;
     _feeling = feeling;
 }


 [HttpGet]
 public WeatherForecast Get()
 {
     // 实例化一个对象实例
     //WeatherForecast weather = WeatherForecast.GetInstance();

     // 多线程去调用
     for (int i = 0; i < 3; i++)
     {
         var th = new Thread(
         new ParameterizedThreadStart((state) =>
         {
             //WeatherForecast.GetInstance();

             // 此刻的心情
             Console.WriteLine(_feeling.Date);
         })
         );
         th.Start(i);
     }
     return null;
 }

测试的结果,情理之中,只在我们项目初始化服务的时候,进入了一次构造函数:

Es ist dasselbe wie das, was wir oben gesagt haben: Singleton ist ein Singleton und es ist auch ein Sperrtyp mit doppelter Überprüfung, da die Schlussfolgerung ersichtlich ist, dass wir dies direkt tun können, wenn wir den Singleton-Modus verwenden Die Verwendung der Abhängigkeitsinjektion von Sigleton kann ausreichen und ist sehr praktisch.

4. Vor- und Nachteile des Singleton-Modus

[Ausgezeichnet], Vorteile des Singleton-Modus:

(1) Eindeutigkeit sicherstellen: Andere Objekte verhindern von der Instanziierung ab und stellen Sie die Einzigartigkeit der Instanz sicher; (2) Globalität: Nachdem die Daten definiert wurden, können die aktuelle Instanz und die Daten überall im gesamten Projekt verwendet werden; ] Nachteile des Singleton-Modus:

(1) Speicherresident: Da der Singleton den längsten Lebenszyklus hat und im gesamten Entwicklungssystem vorhanden ist, führt dies zu bestimmten Problemen, wenn ständig Daten hinzugefügt werden oder resident sind Menge des Speicherverbrauchs.

Der folgende Inhalt stammt aus der Baidu-Enzyklopädie:

Vorteile

1. Instanzkontrolle

Der Singleton-Modus wird andere verhindern Das Objekt instanziiert seine eigene Kopie des Singleton-Objekts und stellt so sicher, dass alle Objekte auf die eindeutige Instanz zugreifen.

2. Flexibilität

Da die Klasse den Instanziierungsprozess steuert, kann die Klasse den Instanziierungsprozess flexibel ändern. Nachteile

1. Overhead

Obwohl die Anzahl gering ist, wenn jedes Mal, wenn das Objekt eine Referenz anfordert, eine Instanz der Klasse vorhanden ist wird dennoch ein gewisser Overhead erforderlich sein. Dieses Problem kann durch Verwendung der statischen Initialisierung gelöst werden.

2. Mögliche Entwicklungsverwirrung

Bei der Verwendung von Singleton-Objekten (insbesondere in Klassenbibliotheken definierten Objekten) müssen Entwickler bedenken, dass sie nicht neu verwenden können Schlüsselwort instanziiert Objekte. Da der Quellcode der Bibliothek möglicherweise nicht zugänglich ist, können Anwendungsentwickler unerwartet feststellen, dass sie diese Klasse nicht direkt instanziieren können.

3. Objektlebensdauer

kann das Problem des Löschens eines einzelnen Objekts nicht lösen. In Sprachen, die Speicherverwaltung bereitstellen (z. B. solche, die auf dem .NET Framework basieren), kann nur eine Singleton-Klasse dazu führen, dass eine Instanz freigegeben wird, da sie einen privaten Verweis auf die Instanz enthält. In einigen Sprachen (z. B. C++) können andere Klassen Objektinstanzen löschen, dies führt jedoch zu fehlenden Referenzen in der Singleton-Klasse.

Dieser Artikel stammt von der chinesischen PHP-Website, Kolumne

Java-Tutorial

, willkommen zum Lernen!

Das obige ist der detaillierte Inhalt vonSingleton-Muster und Singleton in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen