|
| Abstract klasse som Singleton Fra : Brian |
Dato : 14-05-05 00:43 |
|
Jeg har en abstract klasse AbstractClass, som skal implementeres som
singleton - hvordan gør man lige det i java?
Problemet ligger i at denne klasse har en funktion som de klasser som
nedarver fra den implementere.
| |
Casper B (14-05-2005)
| Kommentar Fra : Casper B |
Dato : 14-05-05 01:23 |
|
> Jeg har en abstract klasse AbstractClass, som skal implementeres som
> singleton - hvordan gør man lige det i java?
>
> Problemet ligger i at denne klasse har en funktion som de klasser som
> nedarver fra den implementere.
Hvis jeg forstår dit problem rigtigt og såfremt du benytter Java 5/Tiger:
public class ConcreteClass extends AbstractClass
{
private volatile static ConcreteClass inst;
private ConcreteClass();
public static ConcreteClass getInstance()
{
if(inst == null)
{
syncronized (ConcreteClass.class)
{
if(inst == null)
inst = new ConcreteClass();
}
}
return inst;
}
public EnEllerAndenMetodeFraAbstractClass()
{
...
}
}
/Casper
| |
Brian (14-05-2005)
| Kommentar Fra : Brian |
Dato : 14-05-05 02:15 |
|
"Casper B" <casper@jbr.dk> wrote in message
news:42854345$0$223$edfadb0f@dread12.news.tele.dk...
>> Jeg har en abstract klasse AbstractClass, som skal implementeres som
>> singleton - hvordan gør man lige det i java?
>>
>> Problemet ligger i at denne klasse har en funktion som de klasser som
>> nedarver fra den implementere.
>
> Hvis jeg forstår dit problem rigtigt og såfremt du benytter Java 5/Tiger:
>
> public class ConcreteClass extends AbstractClass
> {
> private volatile static ConcreteClass inst;
>
> private ConcreteClass();
>
> public static ConcreteClass getInstance()
> {
> if(inst == null)
> {
> syncronized (ConcreteClass.class)
> {
> if(inst == null)
> inst = new ConcreteClass();
> }
> }
> return inst;
> }
>
> public EnEllerAndenMetodeFraAbstractClass()
> {
> ...
> }
> }
>
>
> /Casper
Hvad er Java 5/Tiger?
Hvad er Jeg har en klasse som hedder Language, denne klasse har en abstract
klasse som hedder getText(). Klassen Language laver et objekt af de klasser
som nedarver fra den selv. Language oprette et objekt via reflections.
Endvidere implementere Language et observer pattern - så jeg kan ikke rigtig
se hvordan det skulle kunne lade sig gøre? Har du den bedre ide til et
design?
Brian
| |
Casper B (14-05-2005)
| Kommentar Fra : Casper B |
Dato : 14-05-05 11:08 |
|
> Hvad er Java 5/Tiger?
Sun's Java 1.5 kaldes også for Java 5 samt Tiger. Brug Brian Matzon's
implementation hvis du ikke er intereseret i lazy instantiation eller
benytter en JDK < 1.5. Begge disse to er trådsikre.
> Hvad er Jeg har en klasse som hedder Language, denne klasse har en abstract
> klasse som hedder getText(). Klassen Language laver et objekt af de klasser
> som nedarver fra den selv. Language oprette et objekt via reflections.
> Endvidere implementere Language et observer pattern - så jeg kan ikke rigtig
> se hvordan det skulle kunne lade sig gøre? Har du den bedre ide til et
> design?
Jeg går ud fra du mener "...har en abstrakt metode som hedder
getText()...". Har jeg forstået det korrekt, at du ønsker at din
singleton metode skal nedarves til sub-klasser så disse ligeledes kan
instantieres via dette singleton?
I så fald, er der et par problemer. Siden definitionen på et Singleton
er at konstruktøren er privat (således at "new" ikke kan misbruges), er
første problem at du ikke kan nedarve fra en klasse med en privat
konstruktør. Du kan naturligvis lave denne public, men så kan du jo ikke
længere garantere, at der kun er én instans af dit objekt.
Såfremt du er ok med dette, er næste problem, at et Singleton normalt
implementeres med en statisk variabel som holder instansen. Hvis du
nedarver, vil alle dine specialiserede sub-klasser benytte en og samme
variabel. Dette kan løses ved et mere sofistikeret instans registrering
i din super-klasse.
Normalt anbefales det ikke at sub-klasse et Singleton, i kraft af hvor
relativt nemt det er at implementere disse i eksisterende klasser.
/Casper
| |
Brian Matzon (14-05-2005)
| Kommentar Fra : Brian Matzon |
Dato : 14-05-05 10:01 |
|
Casper B wrote:
> syncronized (ConcreteClass.class)
> {
> if(inst == null)
> inst = new ConcreteClass();
Med mindre at det er et problem at singleton bliver instantieret før tid
vil jeg anbefale ikke at bruger oversående model, men i stedet:
public class ConcreteClass extends AbstractClass {
private volatile static ConcreteClass inst = new ConcreteClass();
private ConcreteClass();
public static ConcreteClass getInstance() {
return inst;
}
public EnEllerAndenMetodeFraAbstractClass() {
...
}
}
Grunden til dette, er at synchronized i overstående tilfælde ikke
virker. Dette har været sandt i hvert fald op til 1.3 - ved ikke om der
er fixed it senere versioner. Men under alle omstændigheder kan du vel
ikke garantere at dit program kun kører på 1.5 VM's ?
/matzon
| |
Michael Berg (16-05-2005)
| Kommentar Fra : Michael Berg |
Dato : 16-05-05 12:56 |
|
Hej alle,
> public class ConcreteClass extends AbstractClass {
> private volatile static ConcreteClass inst = new ConcreteClass();
Er der nogen speciel grund til at bruge volatile? Jeg kan ikke se hvorfor
det er nødvendigt.
/Michael
| |
Filip Larsen (16-05-2005)
| Kommentar Fra : Filip Larsen |
Dato : 16-05-05 16:37 |
|
Michael Berg skrev
> > public class ConcreteClass extends AbstractClass {
> > private volatile static ConcreteClass inst = new ConcreteClass();
>
> Er der nogen speciel grund til at bruge volatile? Jeg kan ikke se
hvorfor
> det er nødvendigt.
Hvis inst tilgås uden brug af synchronized, så er volatile så vidt jeg
kan se princippielt nødvendig for at undgå, at kompileren kan finde på
at optimerer bestemte udtryk til kode der bevirker, at nogle tråde ser
en værdi af inst mens andre tråde ser en anden værdi.
Det er i hvert fald hvad jeg læser ud af JLS på det punkt:
http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930
Mvh,
--
Filip Larsen
| |
Michael Berg (16-05-2005)
| Kommentar Fra : Michael Berg |
Dato : 16-05-05 18:44 |
|
Hej,
> > Er der nogen speciel grund til at bruge volatile? Jeg kan ikke se
> Hvis inst tilgås uden brug af synchronized, så er volatile så vidt jeg
> kan se princippielt nødvendig for at undgå, at kompileren kan finde på
> at optimerer bestemte udtryk til kode der bevirker, at nogle tråde ser
> en værdi af inst mens andre tråde ser en anden værdi.
Så skal alle class variable vel være volatile - det lyder ikke rigtigt?
Jeg prøver at forestille mig en situation hvor en privat class variabel
overhovedet spiller en rolle i forbindelse med tråde, men jeg kan ikke lige
komme på noget.
/Michael
| |
Filip Larsen (16-05-2005)
| Kommentar Fra : Filip Larsen |
Dato : 16-05-05 22:40 |
|
Michael Berg skrev
> > > Er der nogen speciel grund til at bruge volatile? Jeg kan ikke se
>
> > Hvis inst tilgås uden brug af synchronized, så er volatile så vidt
jeg
> > kan se princippielt nødvendig for at undgå, at kompileren kan finde
på
> > at optimerer bestemte udtryk til kode der bevirker, at nogle tråde
ser
> > en værdi af inst mens andre tråde ser en anden værdi.
>
> Så skal alle class variable vel være volatile - det lyder ikke
rigtigt?
Hvis de tilgås af flere tråde uden brug af synchronized og man ønsker,
at alle tråde ikke kan se en "forældet" version (jf. eksemplet i JLS),
så må det vel nødvendigvis være sådan.
> Jeg prøver at forestille mig en situation hvor en privat class
variabel
> overhovedet spiller en rolle i forbindelse med tråde, men jeg kan ikke
lige
> komme på noget.
Hvis du returnerer værdien af en privat variabel uden brug af
synchronized, fx. som ved den nævnte getInstance metode, så kan denne
værdi potentielt være "forældet" i forhold til det andre tråde ser.
Et andet eksempel kan være når man benytter en boolean som flag til
hvornår en tråd skal stoppe:
private volatile boolean running;
public void stopWork() { running = false; }
public void run() {
while (running) {
...
}
}
hvor run kaldes af en tråd og stopWork senere kaldes af en anden tråd.
Uden volatile ville man kunne risikere, at løkken aldrig terminerer
selvom stopWork kaldes fordi begge tråde arbejder på hver deres lokale
kopi af running.
Mvh,
--
Filip Larsen
| |
Michael Berg (17-05-2005)
| Kommentar Fra : Michael Berg |
Dato : 17-05-05 19:54 |
|
Hej,
> > Så skal alle class variable vel være volatile - det lyder ikke
> rigtigt?
>
> Hvis de tilgås af flere tråde uden brug af synchronized og man ønsker,
> at alle tråde ikke kan se en "forældet" version (jf. eksemplet i JLS),
> så må det vel nødvendigvis være sådan.
To tråde kan deles om en instans af klassen, men der er ingen måde hvormed
en af disse tråde kan få adgang til interne, private medlemsvariable. Det
kan kun foregå gennem metodekald, og derfor vil jeg nu fortsat holde fast på
at det ikke er nødvendigt at erklære variablen som volatile.
I de gode gamle "C" dage indikerede volatile at variablens indhold kunne
ændres udenfor programmets indflydelse, for eksempel ved at en
systemfunktion opdaterede indholdet ved hjælp af en pointer reference.
Derfor var det nødvendigt at fortælle compileren at indholdet af variablen
på intet tidspunkt måtte caches, fx. i et register.
Det er den sammenhæng jeg prøver at finde i forhold til Java og de
begrænsninger man har for at lave tilsvarende scenarier her. En
klassevariabel kan mig bekendt ikke ændres uden compilerens vidende, med
mindre vi snakker om JNI. Og så er det for så vidt ligegyldigt om det
foregår i tråde eller ej.
> Hvis du returnerer værdien af en privat variabel uden brug af
> synchronized, fx. som ved den nævnte getInstance metode, så kan denne
> værdi potentielt være "forældet" i forhold til det andre tråde ser.
Værdien er forældet så snart den returneres, og det kan vel ikke være
anderledes. Hvis to tråde arbejder med samme objekt, så har man
synkroniseringsproblemer pr. definition. Volatile løser mig bekendt ikke
noget problem i den retning.
> private volatile boolean running;
>
> public void stopWork() { running = false; }
>
> public void run() {
> while (running) {
> ...
> }
> }
>
> hvor run kaldes af en tråd og stopWork senere kaldes af en anden tråd.
> Uden volatile ville man kunne risikere, at løkken aldrig terminerer
> selvom stopWork kaldes fordi begge tråde arbejder på hver deres lokale
> kopi af running.
Det forstår jeg altså ikke. Vi taler om to tråde der arbejder på samme
objekt, *samme* objekt ikke? Hvor er det den "lokale kopi" optræder?
/Michael
| |
Filip Larsen (17-05-2005)
| Kommentar Fra : Filip Larsen |
Dato : 17-05-05 22:43 |
|
Michael Berg skrev
> To tråde kan deles om en instans af klassen, men der er ingen måde
hvormed
> en af disse tråde kan få adgang til interne, private medlemsvariable.
Det
> kan kun foregå gennem metodekald, og derfor vil jeg nu fortsat holde
fast på
> at det ikke er nødvendigt at erklære variablen som volatile.
Vi snakker vist lidt forbi hinanden. Det er klart, at man kan undlade at
bruge volatile hvis man undlader at skrive kode hvor det er nødvendigt
at bruge den. Hvis en klasse har private variable og al tilgang til
denne variabel sker via synchronized metoder så er volatile ikke
nødvendig. Volatile er kun nødvendig hvis flere tråde tilgår en variabel
uden brug af synchronized.
> I de gode gamle "C" dage indikerede volatile at variablens indhold
kunne
> ændres udenfor programmets indflydelse, for eksempel ved at en
> systemfunktion opdaterede indholdet ved hjælp af en pointer reference.
> Derfor var det nødvendigt at fortælle compileren at indholdet af
variablen
> på intet tidspunkt måtte caches, fx. i et register.
Det same gælder for Java. Bemærk, at volatile også er et flag den
virtuelle maskine skal overholde.
> Det er den sammenhæng jeg prøver at finde i forhold til Java og de
> begrænsninger man har for at lave tilsvarende scenarier her. En
> klassevariabel kan mig bekendt ikke ændres uden compilerens vidende,
med
> mindre vi snakker om JNI. Og så er det for så vidt ligegyldigt om det
> foregår i tråde eller ej.
Når du har flere tråde kan kompileren jo ikke udregne alle de flettet
forløb af disse tråde, så her må programmøren træde til og hjælpe. Lad
mig tage eksemplet fra mit forrige indlæg:
private boolean running = true;
...
public void run() {
while (running) {
// kode der ikke ændre running
}
}
Når kompileren ser denne kode, må den så optimere på, at running
tilsyneladende aldrig ændre sig? Hvis run er den eneste public metode på
klassen så må den åbenbart godt, men hvad nu hvis man tilføjer metoden
public void stopWork() {
running = false;
}
så må den åbenbar ikke og skal endda sikre, at hver gang running læses
så sker det fra runnings lagerplads og ikke et register eller lign. For
at gøre det muligt at have optimeret udførsel af variabel referencer og
alligevel tillade brug som ovenfor har man altså benyttet sig volatile
markering.
Eksemplet er også meget godt fordi det viser, at synchronized ikke altid
er en fordel. Hvis man naivt satte synchronized på både run og stopWork
så ville stopWork jo låse for evig når først run kører og man bliver
derfor nødt til at slippe låsen i run en gang imellem, fx. ved at
benytte en hjælpemetode
public synchronized stopWork() {
running = false;
}
public synchronized boolean isRunning() {
return running;
}
public void run() {
while (isRunning()) {
...
}
}
Det betyder så, at run metoden bliver nødt til at låse for hvert
gennemløb, noget der alt andet lige er "spild af tid".
Bemærk i øvrigt, at tildeling af en variabel (undtagen long og double)
altid sker atomisk uanset om der er brugt synchronized eller ej, så
synchronized er egentlig mest til for at sikre sammenhæng mellem flere
variable end det er til at sikre atomisk læsning/skrivning af en enkel
variabel.
> Det forstår jeg altså ikke. Vi taler om to tråde der arbejder på samme
> objekt, *samme* objekt ikke? Hvor er det den "lokale kopi" optræder?
Ud over "kopier" i registre som ovenfor, så er der også trådlokalt lager
i den virtuelle maskine. Lad mig citerer fra JVM spec'en (2. udgave,
side 59):
"Each thread has a working memory, in which it may keep copies of the
values of variables from the main memory that are shared between all
threads. To access a shared variable, a thread usually first obtains a
lock and flushes its working memory. This guarantees that shared values
will thereafter be loaded from the shared main memory to the working
memory of the thread. By unlocking a lock, a thread guarantees that the
values held by the thread in its working memory will be written back to
the main memory."
I den forbindelse betyder volatile så åbenbart, at en variabel ikke må
lægges i trådlokalt lager.
Mvh,
--
Filip Larsen
| |
Brian (16-05-2005)
| Kommentar Fra : Brian |
Dato : 16-05-05 16:39 |
|
"Brian Matzon" <brian@matzon.dk> wrote in message
news:4285be26$0$63590$edfadb0f@dread15.news.tele.dk...
> Casper B wrote:
>> syncronized (ConcreteClass.class)
>> {
>> if(inst == null)
>> inst = new ConcreteClass();
>
> Med mindre at det er et problem at singleton bliver instantieret før tid
> vil jeg anbefale ikke at bruger oversående model, men i stedet:
>
> public class ConcreteClass extends AbstractClass {
> private volatile static ConcreteClass inst = new ConcreteClass();
>
> private ConcreteClass();
>
> public static ConcreteClass getInstance() {
> return inst;
> }
>
> public EnEllerAndenMetodeFraAbstractClass() {
> ...
> }
> }
>
> Grunden til dette, er at synchronized i overstående tilfælde ikke virker.
> Dette har været sandt i hvert fald op til 1.3 - ved ikke om der er fixed
> it senere versioner. Men under alle omstændigheder kan du vel ikke
> garantere at dit program kun kører på 1.5 VM's ?
>
> /matzon
Det er vel for at sikre at det altid er det opdatede objekt man arbejdet på?
| |
Filip Larsen (14-05-2005)
| Kommentar Fra : Filip Larsen |
Dato : 14-05-05 15:17 |
|
Casper B skrev
> > Jeg har en abstract klasse AbstractClass, som skal implementeres som
> > singleton - hvordan gør man lige det i java?
> >
> > Problemet ligger i at denne klasse har en funktion som de klasser
som
> > nedarver fra den implementere.
>
> Hvis jeg forstår dit problem rigtigt og såfremt du benytter Java
5/Tiger:
>
> public class ConcreteClass extends AbstractClass
> {
> private volatile static ConcreteClass inst;
>
> private ConcreteClass();
>
> public static ConcreteClass getInstance()
> {
> if(inst == null)
> {
> syncronized (ConcreteClass.class)
> {
> if(inst == null)
> inst = new ConcreteClass();
> }
> }
> return inst;
> }
>
> public EnEllerAndenMetodeFraAbstractClass()
> {
> ...
> }
> }
Som det allerede er nævnt, så er der ingen garanti for, at Double
Checked Locking mønsteret virker efter hensigten når man skal en
singleton (se [1]). Jeg vil anbefale, at man her blot sætter
synchronized på metodeniveau istedet.
Desuden, som jeg læser det oprindelige spørgsmål så skal singleton
instansen være på AbstractClass niveau (dvs. der skal være netop en
instans af en AbstractClass underklasse tilgængelig på AbstractClass),
og i så fald skal man gøre noget henad:
public abstract class AbstractClass {
public static AbstractClass getInstance() {
return instance;
}
protected AbstractClass() {
setInstance(this);
}
private volatile static AbstractClass instance;
private synchronized void setInstance(AbstractClass impl) {
if (instance == null) {
instance = impl;
}
}
}
class ConcreteClass1 extends AbstractClass {
public ConcreteClass1() {
}
}
class ConcreteClass2 extends AbstractClass {
public ConcreteClass2() {
}
}
Ovenstående ophøjer den først instantierede instans til at være
singleton. Man kan naturligvis lave anden opførsel ved at rette/udvide
setInstance(), og fx. kombinere med getInstance på de konkrete klasser.
[1] http://www.google.com/search?q=double+checked+locking
Mvh,
--
Filip Larsen
| |
Brian (14-05-2005)
| Kommentar Fra : Brian |
Dato : 14-05-05 22:40 |
|
Nu har jeg ændret en del i mit design - og det virker. Jeg kan lige hurtigt
beskrive hvad jeg gør.
Jeg har oprettet en sigletion klasse LanguageManager som håndtere opretning
af sprog objektet, også har jeg sat et observer pattern på sprog klasse
variablen, som sørger for at staten er ens over alt i hele applicationen.Og
dette virker
Tak for hjælpen alle sammen - det er lækkert at man kan få hjælp her når man
sidder fast.
| |
|
|