Table of Contents
Johdanto: Singleton-malli monilukuisissa konepajasovelluksissa
Singleton-malli on yksi yleisimmin käytetyistä luomusmallien suunnittelumalleista ohjelmistotekniikassa. Se varmistaa, että luokassa on vain yksi esimerkki ja että se tarjoaa globaalin pääsyn kyseiseen esimerkkiin. Yksisäikeisissä sovelluksissa yhden ainoan ruudun toteuttaminen on yksinkertaista: rakennuttajan yksityisyyttä, joka tarjoaa staattisen menetelmän, joka palauttaa yhden instanssin, joka on luotu innokkaasti tai laiskasti. Monisäikeisissä sovelluksissa . Kuitenkin esimerkiksi sulautettujen järjestelmien, korkean taajuuden kauppaalustojen, reaaliaikaisten ohjausjärjestelmien ja hajautettujen tietokantojen kaltaiset ongelmat tulevat huomattavasti monimutkaisemmiksi. Kiertoturvallisuus, muistinnäkyvyys ja suorituskykyrajoitukset vaativat huolellista suunnittelua. Virheet singletonin toteutuksessa voivat johtaa kilpailuolosuhteisiin, monimuotoluontiin, umpikujiin tai subtle bugeihin, jotka ovat tunnetusti vaikeasti toistuvia ja debug.
Tässä artikkelissa tarkastellaan yleisimpiä virheitä kehittäjiä Singletonin mallin toteuttamisessa monisäikeisissä ympäristöissä, selvitetään taustalla olevia syitä ja tarjotaan kattava joukko parhaita käytäntöjä ja malleja niiden välttämiseksi. Se sisältää myös käytännön esimerkkejä Javasta, viitaten vastaaviin kuvioihin C++:ssa ja C#:ssä, ja suosittelee ulkoisia resursseja jatkolukemiseen.
Yhteiset virheet Singletonin täytäntöönpanossa
Jopa kokeneet kehittäjät voivat joutua ansaan, kun he toteuttavat singletoneja samanaikaisesti. Alla ovat yleisimmät virheet, joista jokaisella on selitys siihen, miksi ne ovat vaarallisia.
1. Ei tehdä Kastaja yksityiseksi
Singleton-sopimuksen perusta on yksityinen rakentaja, joka estää ulkoisen instantaation. Jos rakentaja on käytettävissä (julkinen, suojattu tai paketti-yksityisen), mikä tahansa lanka voi luoda uuden instanssin, rikkoen singleton-sopimuksen. Monisäikeisessä koodissa tämä voi tapahtua tahattomasti, kun luokka on korjattu ja rakentajan näkyvyys on vahingossa muuttunut tai kun luokka on alaluokka (vaikka alaluokka on yleensä lannistunut). Ilmoita aina rakentaja yksityiseksi, ja jos sinun täytyy tukea alaluokkia (harvinaisesti), käytä suojattua rakentajaa äärimmäisen varovaisesti ja dokumentoi odotettua käyttäytymistä.
2. Epäonnistuminen käsitellä säikeen turvallisuutta
Yksisäikeisessä ympäristössä yksinkertainen laiska alustus toimii hyvin:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Mutta monisäikeisessä sovelluksessa kaksi tai useampia säikeitä voi samanaikaisesti tulla [ tarkista ennen kuin jokin lanka on luonut instanssin. Jokainen säikeen sitten etenee luodakseen oman [ objektin, joka rikkoo kaavaa. Tämä on klassinen ]-rasituksen tila[], joka johtaa useisiin tapauksiin ja voi johtaa epäjohdonmukaisiin tila- tai resurssivuotoihin.
3. Laiskan alustuksen käyttäminen ilman asianmukaista synkronointia
Jopa kehittäjät, jotka tunnistavat tarpeen lanka turvallisuuden usein lisää synkronointi naiivisti. Esimerkiksi synkronointi koko [ menetelmä toimii, mutta tuo esiin suorituskyky pullonkaulan:
public static synchronized Singleton getInstance() { ... }
Jokainen kutsu ostaa ja vapauttaa lukon, vaikka instanssi on jo luotu. Korkean sisällön skenaarioissa tämä yläpuolella voi vakavasti heikentää läpimenoa. Parempi lähestymistapa on käyttää [ kaksinkertaisesti tarkastettua lukitusta[] (keskustelussa on käsitelty alla), mutta jopa tuolla kuviolla on sudenkuoppia, jos sitä ei panna asianmukaisesti täytäntöön.
4. Synkronoinnin ylikäyttö
Synkronointi tulee monissa muodoissa: [ menetelmät, lohkot, , [, [], ja niin edelleen. Ylisynkronointi. Karkeilla rakeilla sulkujen käyttäminen, kun hienoksi grainoitu ohjaus on käytettävissä, johtaa tarpeettomaan kiistaan. Joissakin konesovelluksissa (esim. reaaliaikaisissa järjestelmissä, joissa on tiukka latenssibudjetti), jopa muutama sata nanosekuntia lukituksen yläpuolella voi olla mahdotonta hyväksyä. Tavoitteena on minimoida kriittinen osio samalla kun varmistamme lankaturvallisuuden.
5. Huomioimatta [volatiilia avainsana
Javan, C#:n ja C++:n kaltaisilla kielillä ) [volatiili[] avainsana (tai vastaava) on välttämätön monisäikeisen koodin oikean näkyvyyden kannalta. Ilman sitä kääntäjä tai suoritin voi tilata uudelleen ohjeita, ja yhden langan tekemät muutokset eivät välttämättä ole näkyvissä toiselle. Kaksoistarkkaissussa lukitusmallissa singleton-esimerkkiä ei ole ilmoitettu voi aiheuttaa säikeen nähdä osittain rakennetun objektin, mikä johtaa arvaamattomaan käyttäytymiseen. Tämä on yksi hienovaraisimmista ja vaarallisimmista virheistä.
Parhaat käytännöt Singletonin täytäntöönpanossa
Välttääkseen nämä sudenkuopat, noudata näitä todistettuja strategioita. Jokainen lähestymistapa koskee lanka turvallisuutta, suorituskykyä ja yksinkertaisuutta.
Yksityinen rakennelma ja staattinen instantaatio
Alustamisstrategiasta riippumatta rakentajan on oltava yksityinen. Singleton-esiintymä on säilytettävä staattisessa kentässä. Älä paljasta rakennuttajaa millään tavalla ja harkitse luokan tekemistä Javassa (tai ] C#:ssa) alaluokan välttämiseksi.
Käytä synkronoituja lohkoja vain, kun ne ovat välttämättömiä
Laiska alustaminen, kaksinkertainen-tarkistettu lukituskuvio vähentää synkronointi yläpuolella:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
Singleton result = instance; // Local variable for performance
if (result == null) {
synchronized (Singleton.class) {
result = instance;
if (result == null) {
instance = result = new Singleton();
}
}
}
return result;
}
}
Tässä koodissa tarkistus synkronoidun lohkon ulkopuolella välttää lukituksen yläpuolella, kun instanssi on jo olemassa. Sisätarkistus varmistaa, että vain yksi lanka luo instanssin. avainsana estää ohjeen uudelleenjärjestämisen ja varmistaa, että toimeksianto [ on täysin näkyvissä muille langoille. Huomaa, että välitämme instanssin paikallisessa muuttujassa suorituskykyä varten. Tämä kuvio on oikea Java 5+:ssa (asianmukaisella muistimallilla) ja toimii samalla tavalla C#:ssa ja C++:ssa (käyttäen :ssa ja muistijärjestyksessä).
Eager- alustus
Jos singleton on aina tarpeen ja luomistyö on halpaa, innokas alustaminen on yksinkertaisin lankaturvallinen lähestymistapa:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
Luokkakuormaus on luonnostaan JVM:n synkronoimaa, joten lisäkoordinaatiota ei tarvita. Tämä kuitenkin luo tapahtuman luokkakuormitusajalla, joka voi olla epämieluisaa resurssirajoitetuissa järjestelmissä tai kun singleton riippuu vielä käytettävissä olevasta ajoaikakonfiguraatiosta.
Staattisen haltijan kuvio (initialisointi päälle-tilattu)
Tämä kuvio yhdistää laiska alustaminen lanka turvallisuus ilman nimenomaista synkronointia:
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
luokka on ladattu vain silloin, kun on ensin kutsuttu, ja JVM takaa staattisen kentän turvallisen julkaisun luokkakuormauksen aikana. Tätä pidetään yleisesti Java singletonsin tyylikkäimpänä ratkaisuna.
Enum-Based Singleton (Java)
Joshua Bloch... Effektiivinen Java suosittelee enumin käyttöä:
public enum Singleton {
INSTANCE;
// methods and fields
}
Enum vakiot ovat implisiittisesti , ja Java kieli takaa, että enum tapauksia luodaan vain kerran, jopa sarjan tai heijastushyökkäykset. Tämä on sekä lanka-turvallinen ja ytimekäs. Kuitenkin enums ei voi laajentaa luokkia (vain toteuttaa rajapintoja), joten ne eivät sovellu kaikkiin käyttötapauksiin.
Vastaavat kuviot C++- ja C#-muodossa
C++:ssa Meyer.s Singleton (paikallinen staattinen alustus) on kierreturvallinen C+++11:n jälkeen.
Singleton& getInstance() {
static Singleton instance;
return instance;
}
C#:ssa -luokassa on sisäänrakennettu langaton ja laiska alustaminen:
public class Singleton {
private static readonly Lazy<Singleton> _lazy =
new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance => _lazy.Value;
}
Testaus ja havainnointi konepajasovelluksissa
Teknisissä sovelluksissa singleton-malli hallinnoi usein yhteisiä resursseja, kuten laitteiston ajureita, konfiguraatio-asetuksia, kierrealtaita tai puunkorjuupalveluja. Tällaisten singleton-mallien testaaminen monisäikeisissä testeissä vaatii huolellista suunnittelua.
- Tee singletons testattavissa[] tarjoamalla tapa palauttaa instanssi (esim. suojattu menetelmä, jota käytetään vain testeissä) tai ruiskuttamalla riippuvuuksia käyttöliittymän kautta. Monet modernit sovellukset välttävät singletoneja kokonaan elinkaarta hallitsevien riippuvuusruiskutusjärjestelmien hyväksi.
- ] Suoritusprofilointi[ reaaliajassa tai suurtaajuusjärjestelmissä: mittaa synkronoinnin yläpuolella. Joissakin tapauksissa lukkoton singleton, jossa käytetään (C#) tai (C+) voidaan perustella.
- Jakautuneet järjestelmät[ edellyttävät singletoneja olla ainutlaatuisia prosessia kohti, ei eri prosesseja. Jos tarvitset klusteri-laaja singleton, käytä ulkoista koordinointia (esim. tietokanta, ZooKeeper, tai johtajavaalit).
- ]Taivue ja serialisointi[] voi rikkoa singletoneja. Käytä [] Javan serialisoinnissa ja estä heijastava instanssien heittäminen heittämällä poikkeus rakennukseen, jos on jo asetettu.
Päätelmät
Singletonin malli on edelleen arvokas työkalu ohjelmistoinsinöörin työkalupakissa, mutta sen toteuttaminen monisäikeisissä ympäristöissä vaatii tarkkaa huomiota yksityiskohtiin. Ymmärtämällä ja välttämällä yhteisiä virheitä.Tällaisia ovat esimerkiksi muut kuin yksityiset rakentajat, puuttuvat synkronointi, epäasianmukainen haihtuva käyttö ja ylisynkronisointi. C++ ja C#:n modernit kieliominaisuudet voivat tuottaa vankkaa, korkeatehoista singletoneja. Kaksotarkennettu lukituskuvio, staattinen haltijakuvio ja enum-pohjaiset singletonit Javassa tarjoavat vakaan tasapainon turvallisuuden ja tehokkuuden suhteen. C++ ja C#:ssä modernit kieliominaisuudet yksinkertaistavat tehtävää edelleen.
Lisätietoja on saatavilla seuraavista resursseista:
- [LLT:0]]Wikipedia: Singleton-kuvio[[LLT:1]]
- Oracle Java Singleton Tutorial
- Kaksinkertainen lukitus on rikki" -ilmoitus
- Microsoft.NET Singleton kuvio
Lopulta paras singleton toteutus on se, joka on yksinkertaisin vaatimuksiin. Kun epäilykset, mieluummin innokas alustaminen tai staattinen haltija kuvio, ja aina kirjoittaa samanaikaisia yksikön testejä validoida oikeellisuuden väitteen.