Singleton-malli on petollisen yksinkertainen, tasainen ja erityisesti Java-kehittäjille. Tässä klassikossa JavaWorld artikkeli, David Geary osoittaa, kuinka Java-kehittäjät toteuttavat singletoneja koodiesimerkkien avulla monisäikeisiin, luokkakuormaajiin ja sarjallisuuteen Singleton-mallin avulla. Hän päättää tarkastelemalla yksirekisterirekisterien toteuttamista singletonien määrittämiseksi ajon aikana.
Joskus on tarkoituksenmukaista, että luokassa on täsmälleen yksi esiintymä: ikkunaohjaimet, tulostus taustatulostimet ja tiedostojärjestelmät ovat prototyyppisiä esimerkkejä. Tyypillisesti tämän tyyppisiin kohteisiin, joita kutsutaan yksinäisiksi, pääsee käsiksi erilaisilla esineillä kaikkialla ohjelmistojärjestelmässä, ja siksi ne edellyttävät globaalia pääsypistettä. Tietenkin juuri silloin, kun olet varma, et koskaan tarvitse enempää kuin yhtä esiintymää, se on hyvä veto, että muutat mieltäsi.
Singletonin suunnittelukuvio käsittelee kaikkia näitä huolenaiheita. Singleton-mallikuvion avulla voit:
- Varmista, että luokkaan luodaan vain yksi esiintymä
- Anna objektille maailmanlaajuinen pääsypiste
- Salli useita esiintymiä tulevaisuudessa vaikuttamatta yksittäisen luokan asiakkaisiin
Vaikka Singletonin suunnittelumalli - kuten alla olevasta kuvasta käy ilmi - on yksi yksinkertaisimmista suunnittelumalleista, se tuo esiin useita karhuja varomattomalle Java-kehittäjälle. Tässä artikkelissa käsitellään Singletonin suunnittelumallia ja puututaan näihin sudenkuoppiin.
Lisätietoja Java-suunnittelumalleista
Voit lukea kaikki David Gearyn Java Design Patterns -sarakkeettai tarkastele JavaWorld-luetteloa viimeisimmät artikkelit Java-suunnittelumalleista. Katso "Suunnittelumallit, iso kuva"keskusteluun Neljä joukko -mallien käytön eduista ja haitoista. Haluatko lisää? Hanki Enterprise Java -uutiskirje postilaatikkoosi.
Singleton-kuvio
Sisään Suunnittelumallit: Uudelleenkäytettävien olio-ohjelmistojen elementit, Neljän jengi kuvaa Singleton-mallia näin:
Varmista, että luokassa on vain yksi esiintymä, ja anna sille yleinen pääsypiste.Alla oleva kuva havainnollistaa Singletonin suunnittelukuvion luokkakaaviota.
Kuten näette, Singletonin suunnittelumallissa ei ole paljon. Yksittäiset henkilöt ylläpitävät staattista viittausta ainoaan yksittäiseen esiintymään ja palauttavat viitteen kyseiseen esiintymään staattisesta ilmentymä()
menetelmä.
Esimerkki 1 esittää klassisen Singleton-mallin toteutuksen:
Esimerkki 1. Klassinen singletti
julkinen luokka ClassicSingleton {yksityinen staattinen ClassicSingleton-ilmentymä = null; suojattu ClassicSingleton () {// Olemassa vain estämään instantiation. } public staattinen ClassicSingleton getInstance () {if (instance == null) {instance = new ClassicSingleton (); } return-ilmentymä; }}
Esimerkissä 1 toteutettu yksikkö on helppo ymmärtää. ClassicSingleton
luokka ylläpitää staattista viittausta yksinäiseen yksittäistapaukseen ja palauttaa viitteen staattisesta getInstance ()
menetelmä.
On useita mielenkiintoisia kohtia liittyen ClassicSingleton
luokassa. Ensimmäinen, ClassicSingleton
käyttää tekniikkaa, joka tunnetaan nimellä laiska instantiation luoda singleton; tämän seurauksena singleton-ilmentymä luodaan vasta getInstance ()
menetelmää kutsutaan ensimmäistä kertaa. Tämä tekniikka varmistaa, että yksittäisiä esiintymiä luodaan vain tarvittaessa.
Toiseksi, huomaa se ClassicSingleton
toteuttaa suojatun rakentajan, joten asiakkaat eivät voi ilmetä ClassicSingleton
tapaukset; Saatat kuitenkin olla yllättynyt huomatessasi, että seuraava koodi on täysin laillinen:
julkinen luokka SingletonInstantiator {public SingletonInstantiator () {ClassicSingleton instance = ClassicSingleton.getInstance (); ClassicSingleton anotherInstance =uusi ClassicSingleton (); ... } }
Kuinka edellisen koodin luokka voi katkaista - joka ei ulotu ClassicSingleton
-luo ClassicSingleton
esimerkiksi jos ClassicSingleton
rakentaja on suojattu? Vastaus on, että suojattuja rakentajia voidaan kutsua alaluokkiin ja muiden luokkien samassa paketissa. Koska ClassicSingleton
ja Yksittäinen välittäjä
ovat samassa paketissa (oletuspaketti), Yksittäinen välittäjä ()
menetelmät voivat luoda ClassicSingleton
tapauksia. Tässä dilemmassa on kaksi ratkaisua: Voit tehdä ClassicSingleton
rakentaja yksityinen niin vain KlassinenSingleton ()
menetelmät kutsuvat sitä; se tarkoittaa kuitenkin ClassicSingleton
ei voida luokitella alaluokkiin. Joskus se on toivottava ratkaisu; jos on, on hyvä julistaa yksinäisyystunnisi lopullinen
, mikä tekee tarkoituksesta selkeän ja antaa kääntäjän soveltaa suorituskyvyn optimointeja. Toinen ratkaisu on laittaa singleton-luokkasi eksplisiittiseen pakettiin, joten muiden pakettien luokat (mukaan lukien oletuspaketti) eivät voi ilmentää singleton-ilmentymiä.
Kolmas mielenkiintoinen asia ClassicSingleton
: On mahdollista saada useita yksittäisiä esiintymiä, jos eri luokkakuormaajien lataamat luokat käyttävät yksikköä. Tuo skenaario ei ole niin haettu; esimerkiksi jotkut servlet-kontit käyttävät erillisiä luokkakuormaajia kullekin servletille, joten jos kaksi servletiä käyttää singlettiä, niillä on jokaisella oma ilmentymä.
Neljänneksi, jos ClassicSingleton
toteuttaa java.io.Serialisoitavissa
käyttöliittymän avulla luokan instanssit voidaan sarjoittaa ja deserialisoida. Jos kuitenkin sarjalliset yksittäisen objektin ja deserialisoit sen myöhemmin useammin kuin kerran, sinulla on useita yksittäisiä esiintymiä.
Lopuksi ja ehkä tärkein esimerkki 1 ClassicSingleton
luokka ei ole langankestävä. Jos kaksi ketjua - kutsumme heitä säie 1 ja lanka 2 - kutsuu ClassicSingleton.getInstance ()
samaan aikaan kaksi ClassicSingleton
ilmentymiä voidaan luoda, jos lanka 1 on ennalta asetettu heti sen saapumisen jälkeen jos
lohko ja ohjaus annetaan sen jälkeen langalle 2.
Kuten edellisestä keskustelusta käy ilmi, vaikka Singleton-malli on yksi yksinkertaisimmista suunnittelumalleista, sen toteuttaminen Java-ohjelmassa on kaikkea muuta kuin yksinkertaista. Tämän artikkelin loppuosa käsittelee Java-spesifisiä näkökohtia Singleton-mallille, mutta ensin otetaan lyhyt kiertotie nähdäksesi, kuinka voit testata singleton-luokkiasi.
Testaa yksittäisiä
Tämän artikkelin loppuosassa käytän JUnitia yhdessä log4j: n kanssa testattaakseni yksittäisiä luokkia. Jos et tunne JUnitia tai log4j: tä, katso Resurssit.
Esimerkissä 2 luetellaan JUnit-testitapaus, joka testaa esimerkin 1 singletonin:
Esimerkki 2. Yksittäistesti
tuo org.apache.log4j.Logger; tuo junit.framework.Assert; tuo junit.framework.TestCase; julkinen luokka SingletonTest laajentaa TestCase {yksityinen ClassicSingleton sone = null, stwo = null; yksityinen staattinen Logger logger = Logger.getRootLogger (); julkinen SingletonTest (merkkijono) {super (nimi); } public void setUp () {logger.info ("singletonin hakeminen ..."); sone = ClassicSingleton.getInstance (); logger.info ("... sai singleton:" + sone); logger.info ("singletonin saaminen ..."); stwo = ClassicSingleton.getInstance (); logger.info ("... sai singleton:" + stwo); } public void testUnique () {logger.info ("singletonien tasa-arvon tarkistaminen"); Assert.assertEquals (true, sone == stwo); }}
Esimerkin 2 testitapaus vetoaa ClassicSingleton.getInstance ()
kahdesti ja tallentaa palautetut viitteet jäsenmuuttujiin. testUnique ()
menetelmä tarkistaa, että viitteet ovat identtiset. Esimerkki 3 osoittaa, että testitulos:
Esimerkki 3. Testitapaustulos
Koontitiedosto: build.xml init: [echo] Koontiversio 20030414 (14.4.2003 03:08) compile: run-test-text: [java] .INFO main: saada singleton... [java] INFO pää: luonut singletonin: Singleton @ e86f41 [java] INFO main: ... sai singleton: Singleton @ e86f41 [java] INFO main: saada singleton.
Kuten edellinen luettelo havainnollistaa, esimerkin 2 yksinkertainen testi läpäisee lentävät värit - kaksi yksittäistä viittausta, jotka on saatu ClassicSingleton.getInstance ()
ovat todellakin samanlaisia; nämä viittaukset saatiin kuitenkin yhdellä säikeellä. Seuraava osa testaa yksinkertaisuusluokkamme useilla säikeillä.
Monisäikeiset näkökohdat
Esimerkit 1 ClassicSingleton.getInstance ()
menetelmä ei ole langankestävä seuraavan koodin takia:
1: if (instanssi == null) {2: instanssi = uusi Singleton (); 3:}
Jos ketju on ennalta asetettu rivillä 2 ennen tehtävän tekemistä, ilmentymä
jäsenmuuttuja on edelleen tyhjä
, ja toinen ketju voi myöhemmin syöttää jos
lohko. Tällöin luodaan kaksi erillistä yksittäistä esiintymää. Valitettavasti tätä skenaariota esiintyy harvoin ja sitä on siksi vaikea tuottaa testauksen aikana. Tämän venäläisen ruletin havainnollistamiseksi olen pakottanut ongelman toteuttamalla esimerkin 1 luokan uudelleen. Esimerkki 4 esittää tarkistetun singleton-luokan:
Esimerkki 4. Pinoa kansi
tuo org.apache.log4j.Logger; julkinen luokka Singleton {yksityinen staattinen Singleton singleton = null; yksityinen staattinen Logger logger = Logger.getRootLogger (); yksityinen staattinen looginen firstThread = tosi; suojattu Singleton () {// Olemassa vain päihittämisen estämiseksi. } julkinen staattinen Singleton getInstance () { if (singleton == null) {simuloiRandomActivity (); singleton = uusi Singleton (); } logger.info ("luotu singleton:" + singleton); paluu singleton; } yksityinen staattinen mitätöinti simuloiRandomActivity() { yrittää { if (firstThread) {firstThread = väärä; logger.info ("nukkuva ..."); // Tämän torkutuksen pitäisi antaa toiselle langalle tarpeeksi aikaa // päästä ensimmäisestä langasta.Thread.currentThread (). Lepotila (50); }} catch (InterruptedException ex) {logger.warn ("Lepotila keskeytetty"); }}}
Esimerkin 4 singletti muistuttaa esimerkin 1 luokkaa, paitsi että edellisen listan singletti pinottaa kannen pakottaakseen monisäikeisvirheen. Ensimmäistä kertaa getInstance ()
method kutsutaan, menetelmän kutsunut säie nukkuu 50 millisekuntia, mikä antaa toiselle säikeelle aikaa kutsua getInstance ()
ja luo uusi yksittäinen esiintymä. Kun unilanka herää, se luo myös uuden singleton-ilmentymän, ja meillä on kaksi singleton-esiintymää. Vaikka esimerkin 4 luokka on keksitty, se stimuloi todellista tilannetta, jossa ensimmäinen lanka, joka kutsuu getInstance ()
saa ennalta.
Esimerkki 5 testaa esimerkin 4 singletonin:
Esimerkki 5. Testi, joka epäonnistuu
tuo org.apache.log4j.Logger; tuo junit.framework.Assert; tuo junit.framework.TestCase; julkinen luokka SingletonTest laajentaa TestCase {private static Logger logger = Logger.getRootLogger (); yksityinen staattinen Singleton singleton = nolla; julkinen SingletonTest (merkkijono) {super (nimi); } public void setUp () { singleton = nolla; } public void testUnique () heittää InterruptedException {// Molemmat säikeet kutsuvat Singleton.getInstance (). Säie threadOne = uusi säie (uusi SingletonTestRunnable ()), threadTwo = uusi säie (uusi SingletonTestRunnable ()); threadOne.start ();threadTwo.start (); threadOne.join (); threadTwo.join (); } yksityinen staattinen luokka SingletonTestRunnable toteuttaa Runnable {public void run () {// Hae viittaus singletoniin. Singleton s = Singleton.getInstance (); // Suojaa yksittäinen jäsenmuuttuja // monisäikeisestä pääsystä. synkronoitu (SingletonTest.class) {if (singleton == null) // Jos paikallinen viittaus on tyhjä ... singleton = s; // ... aseta se yksinkertaiseksi} // Paikallisen viitteen on oltava yhtä suuri kuin Singletonin yksi ja // ainoa esiintymä; muuten meillä on kaksi // Singleton-esiintymää. Assert.assertEquals (tosi, s == yksittäinen); } } }
Esimerkin 5 testitapaus luo kaksi ketjua, aloittaa kukin ja odottaa niiden päättymistä. Testitapaus ylläpitää staattista viittausta yksittäistapaukseen, ja jokainen ketju kutsuu Singleton.getInstance ()
. Jos staattista jäsenmuuttujaa ei ole asetettu, ensimmäinen säie asettaa sen yksikköön, joka on saatu kutsun kanssa getInstance ()
, ja staattista jäsenmuuttujaa verrataan tasa-arvon paikalliseen muuttujaan.
Näin tapahtuu, kun testitapaus suoritetaan: Ensimmäinen ketju kutsuu getInstance ()
, tulee jos
estää ja nukkuu. Myöhemmin toinen ketju myös kutsuu getInstance ()
ja luo yksittäisen esiintymän. Toinen säie asettaa sitten staattisen jäsenmuuttujan luomalle ilmentymälle. Toinen säie tarkistaa staattisen jäsenmuuttujan ja paikallisen kopion tasa-arvon ja testi läpäisee. Kun ensimmäinen ketju herää, se luo myös yksittäisen esiintymän, mutta kyseinen säie ei aseta staattista jäsenmuuttujaa (koska toinen säie on jo asettanut sen), joten staattinen muuttuja ja paikallinen muuttuja eivät ole synkronoituja, ja testi sillä tasa-arvo epäonnistuu. Esimerkissä 6 luetellaan esimerkin 5 testitapaustulos: