2011-09-26 3 views
9

J'ai un problème bizarre - j'espère que quelqu'un peut m'expliquer ce qui se passe et une solution de contournement possible. J'implémente un noyau Z80 en Java, et tente de le ralentir en utilisant un objet java.util.Timer dans un thread séparé.Précision de synchronisation Java sous Windows XP et Windows 7

La configuration de base est que j'ai un thread exécutant une boucle d'exécution, 50 fois par seconde. Dans cette boucle d'exécution, cependant, de nombreux cycles sont exécutés, puis wait() est appelée. Le thread Timer externe appellera notifyAll() sur l'objet Z80 toutes les 20ms, simulant une fréquence d'horloge PAL Sega Master System de 3,54 MHz (ish). La méthode que j'ai décrite ci-dessus fonctionne parfaitement sur Windows 7 (j'ai essayé deux machines) mais j'ai aussi essayé deux machines Windows XP et sur les deux, l'objet Timer semble être oversleeping d'environ 50%. Cela signifie qu'une seconde de temps d'émulation prend environ 1,5 seconde sur une machine Windows XP.

J'ai essayé d'utiliser Thread.sleep() au lieu d'un objet Timer, mais cela a exactement le même effet. Je réalise que la granularité du temps dans la plupart des OS n'est pas meilleure que 1ms, mais je peux supporter 999ms ou 1001ms au lieu de 1000ms. Ce que je ne peux pas supporter est 1562ms - Je ne comprends pas pourquoi ma méthode marche bien sur une version plus récente de Windows, mais pas la plus ancienne - j'ai étudié les périodes d'interruption et ainsi de suite, mais ne semble pas ont développé une solution de contournement.

Pourriez-vous s'il vous plaît dites-moi la cause de ce problème et une solution de contournement suggéré? Merci beaucoup.

Mise à jour: Voici le code complet pour une petite application que je construit pour montrer la même question:

import java.util.Timer; 
import java.util.TimerTask; 

public class WorkThread extends Thread 
{ 
    private Timer timerThread; 
    private WakeUpTask timerTask; 

    public WorkThread() 
    { 
     timerThread = new Timer(); 
     timerTask = new WakeUpTask(this); 
    } 

    public void run() 
    { 
     timerThread.schedule(timerTask, 0, 20); 
     while (true) 
     { 
     long startTime = System.nanoTime(); 
     for (int i = 0; i < 50; i++) 
     { 
      int a = 1 + 1; 
      goToSleep(); 
     } 
     long timeTaken = (System.nanoTime() - startTime)/1000000; 
     System.out.println("Time taken this loop: " + timeTaken + " milliseconds"); 
     } 
    } 

    synchronized public void goToSleep() 
    { 
     try 
     { 
     wait(); 
     } 
     catch (InterruptedException e) 
     { 
     System.exit(0); 
     } 
    } 

    synchronized public void wakeUp() 
    { 
     notifyAll(); 
    } 

    private class WakeUpTask extends TimerTask 
    { 
     private WorkThread w; 

     public WakeUpTask(WorkThread t) 
     { 
      w = t; 
     } 

     public void run() 
     { 
      w.wakeUp(); 
     } 
    } 
} 

Toute la classe principale est le fait de créer et de lancer un de ces threads de travail. Sur Windows 7, ce code produit un temps d'environ 999 ms - 1000 ms, ce qui est totalement correct. Exécuter le même pot sur Windows XP produit cependant un temps d'environ 1562ms - 1566ms, et c'est sur deux machines XP séparées que j'ai testé cela. Ils exécutent tous Java 6 mise à jour 27.

Je trouve que ce problème se produit parce que le temporisateur dort pendant 20 ms (assez petite valeur) - si je bosse toutes les boucles d'exécution pendant une seule seconde dans wait wait() - notifierAll() cycle, cela produit le bon résultat - Je suis sûr que les gens qui voient ce que j'essaie de faire (émuler un Sega Master System à 50fps) verront comment ce n'est pas une solution - cela ne donnera pas temps de réponse interactif, en sautant 49 sur 50. Comme je le dis, Win7 s'en sort bien. Désolé si mon code est trop grand :-(

+0

Question intéressante. Serait-il possible pour vous de fournir un extrait de code autonome qui présente ce comportement? – NPE

+0

Je peux vous donner le code source de la minuterie si vous aimez? Je vous donnerais le lot mais le noyau Z80 est assez lourd ;-) – PhilPotter1987

+1

@aix * "extrait de code autonome" * Le code auto-contenu peut être court, mais il ne peut pas (par définition) être un [ extrait] (http://en.wikipedia.org/wiki/Snippet_%28programming%29). Je recommande de poster un [SSCCE] (http://pscode.org/sscce.html). –

Répondre

5

Quelqu'un peut-il s'il vous plaît me dire la cause de ce problème et une solution de contournement suggéré?

Le problème que vous voyez est probablement lié à clock resolution. Certains systèmes d'exploitation (Windows XP et versions antérieures) sont connus pour être trop chargés et être lents avec wait/notify/sleep (interruptions en général).Pendant ce temps, d'autres systèmes d'exploitation (tous les systèmes Linux que j'ai vus) sont excellents pour retourner le contrôle à peu près au moment spécifié.

La solution de contournement? Pour de courtes durées, utilisez une attente en direct (boucle occupée). Pour de longues durées, dormez moins longtemps que vous ne le souhaitez et ensuite attendez le reste.

+1

Juste pour ajouter, lorsque vous utilisez des boucles, utilisez [System.nanoTime()] (http://download.oracle.com/javase/6/docs/api/java/lang/System.html#nanoTime()) plutôt que [System.currentTimeMillis()] (http://download.oracle.com/javase/6/docs/api/java/lang/System.html#currentTimeMillis()) car currentTimeMillis() a la même granularité que Thread. dormir(). [Lien] (http://stackoverflow.com/questions/351565/system-currenttimemillis-vs-system-nanotime) pour une discussion supplémentaire. – prunge

+0

N'existe-t-il pas un moyen de régler la résolution de l'horloge à 1ms sur XP? J'étais sous l'impression qu'il y avait - mais évidemment comme je n'ai pas réussi à faire ainsi je peux me tromper ;-) – PhilPotter1987

+0

Désolé, non. C'est une chose du système d'exploitation. Une de ces façons cachées dans lesquelles un programme Java peut involontairement devenir spécifique à une plate-forme. –

2

je renoncer à la TimerTask et juste utiliser une boucle occupée:

long sleepUntil = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(20); 
while (System.nanoTime() < sleepUntil) { 
    Thread.sleep(2); // catch of InterruptedException left out for brevity 
} 

Le retard de deux millisecondes donne l'OS hôte beaucoup de temps pour travailler sur d'autres choses (Le code de programme restant est beaucoup plus simple.)

Si les deux millisecondes dur codées sont trop d'un instrument contondant, vous pouvez calculer le temps de sommeil requis et utiliser la surcharge Thread.sleep(long, int)

+0

Merci pour ce Barend - je n'avais pas considéré cela. Je l'ai essayé et il est beaucoup plus proche de mon intervalle de temps requis - toujours pas assez cependant. J'ai toujours un sommeil d'environ 90ms en moyenne, même en calculant le temps de sommeil requis et en utilisant Thread.sleep (long, int). Comme je l'ai dit, ça ne me dérange pas d'être en retard d'une ou deux millisecondes mais c'est trop.Je vous remercie pour la suggestion cependant - c'est un bon et quand je rentre du travail et essaye ceci sur Windows 7 je ne doute pas que cela fonctionnera bien - il ne répond toujours pas pourquoi ce problème existe pourtant. – PhilPotter1987

1

Vous pouvez définir la résolution de minuterie sur Windows XP.

http://msdn.microsoft.com/en-us/library/windows/desktop/dd757624%28v=vs.85%29.aspx

Depuis c'est un paramètre de système, vous pouvez utiliser un outil pour définir la résolution afin que vous puissiez vérifier si cela est votre problème.

Essayez ceci et voir si elle aide: http://www.lucashale.com/timer-resolution/

Vous pouvez voir de meilleurs timings sur les versions plus récentes de Windows car, par défaut, la version plus récente pourrait avoir un resserrement des timings. En outre, si vous exécutez une application telle que Windows Media Player, cela améliore la résolution de la minuterie. Donc, si vous écoutez de la musique pendant l'exécution de votre émulateur, vous obtiendrez peut-être de bons moments.

+0

Merci pour cela - mon émulateur est spécifiquement conçu pour être multi-plateforme, et ne peut donc pas compter sur des spécificités Windows telles que celle-ci pour obtenir une bonne résolution temporelle. À la fin, j'ai utilisé un thread endormi, avec du code pour revenir à une boucle d'attente très occupée si le timing est de plus de 1ms toutes les 20ms. Semble fonctionner très bien sur XP maintenant. – PhilPotter1987

+0

Phil - Cela ressemble à une bonne solution multi-plateforme sans avoir à écrire un code de cas spécial pour chaque plate-forme. Agréable – dss539

Questions connexes