2017-07-16 5 views
1

J'écrivais un programme serveur de base lorsque j'utilisais System.out.println pour imprimer mes messages de journal. J'ai écrit un fichier de classe de base qui utilise cela pour écrire dans le journal. Si je devais écrire ce qui suit:La redirection Java PrintStream se comportait de façon inattendue

System.out.println("Hello, world!"); 
System.out.println("Goodbye, world"); 

La sortie souhaitée serait:

Log message - Hello, world! 
Log message - Goodbye, world! 

Ce qui finit par se produire ne correspond pas à la sortie désirée. Au lieu de cela, il sort à la suivante.

Log message - Hello, world! 
Goodbye, world! 

Le code pour la méthode principale:

public static void main(String[] args){ 
    LogManager.start(); 
    System.out.println("Hello, world!"); 
    System.out.println("Goodbye, world!"); 
    LogManager.stop(); 
} 

La classe LogManager commute la valeur par défaut PrintStream qui est imprimé, et conserve une copie de l'ancien pour imprimer des messages journaux. Cependant, "Message de journal -" n'est pas toujours préfixé. Bien que, en dormant pendant 2000ms entre chaque appel println, la sortie ressemble à ce qui suit.

Log message - Hello, world! 
Log message - Goodbye, world!Log message - 

Le code de LogManager est le suivant.

import java.io.ByteArrayOutputStream; 
import java.io.OutputStream; 
import java.io.PrintStream; 

public class LogManager implements Runnable{ 

    private final PrintStream ps; 
    private final OutputStream out; 
    private static boolean cont = true; 

    public static void start(){ 
     OutputStream stdout = new ByteArrayOutputStream(); 
     PrintStream ps = new PrintStream(stdout); 
     Thread th = new Thread(new LogManager(System.out, stdout)); 
     System.setOut(ps); 
     th.start(); 
    } 

    public static void stop(){ 
     cont = false; 
    } 

    public LogManager(PrintStream std, OutputStream out){ 
     this.ps = std; 
     this.out = out; 
    } 

    @Override 
    public void run() { 
     ByteArrayOutputStream baos = (ByteArrayOutputStream) out; 
     while(true){ 
      if(!cont) return; 
      byte[] bytes = baos.toByteArray(); 
      if(bytes.length > 0){ 
       baos.reset(); 
       ps.print("Log message - " + new String(bytes)); 
      } 
     } 
    } 
} 

Quelqu'un pourrait-il me signaler ce que je fais de mal, l'aide serait grandement appréciée. Je voudrais rester à l'écart des bibliothèques, car je souhaite réduire au minimum la taille de mon JAR, sans inclure de paquets supplémentaires, bien que je sache que je n'utilise pas la bibliothèque de quelqu'un d'autre pour réaliser ce que je suis en train de faire.

Répondre

2

Vous avez quelques conditions de course. Tout d'abord, votre programme se termine dès que stop() se termine. Lorsque cela se produit, il pourrait être avant que le thread LogManager a une chance de voir les nouveaux octets qui ont été écrits:

  1. thread principal écrit: « Au revoir, monde \ n »
  2. fil conducteur fixe cont = false
  3. Le thread LogManager voit cont == false et s'arrête avant d'avoir la possibilité d'écrire ses octets.

En outre, vous utilisez baos.toByteArray(), puis en tant qu'action distincte baos.reset(). Que se passe-t-il si quelqu'un écrit quelque chose entre les deux actions? Ils ne seraient pas reflétés dans la variable bytes, mais le reset() les effacerait.

Pour résoudre le premier problème, vous pouvez effectuer une dernière vérification avant votre retour. En d'autres termes, si vous imager refactorisation que toute toByteArray()/reset()/bit println à une méthode readAndPrint(), la déclaration return devient:

if (!cont) { 
    readAndPrint(); // one last read to empty the buffer 
    return; 
} 

Pour résoudre le second problème, vous devez faire le toByteArray() et reset() tout en maintenant un verrou sur boas (qui se verrouille également contre les écritures sur ce flux, puisque toutes les lectures et écritures dans ByteArrayOutputStream sont synchronisées). Cela garantira que personne d'autre ne peut écrire pendant que vous effectuez ces deux actions.

byte[] bytes; 
synchronized (baos) { 
    bytes = baos.toByteArray(); 
    baos.reset(); 
} 
if (bytes.length >) { ... 

De plus, vous devez faire le volatile champ cont, de sorte qu'une écriture dans un thread est toujours vu dans un autre.

Notez que ce qui précède vous permettra toujours de participer à certaines courses. Par exemple, si vous avez deux threads "principaux", vous pouvez imaginer un scénario dans lequel l'un d'eux appelle stop() tandis que l'autre essaie toujours d'imprimer des messages. La solution est de la coordonner d'une manière ou d'une autre de sorte que lorsque vous appelez stop(), tous les threads ont terminé leur journalisation. Le multithreading est un sujet très compliqué et subtil, difficile à apprendre par l'expérimentation. Si vous ne l'avez pas encore fait, je vous suggère fortement de lire un livre ou un tutoriel approfondi pour bien comprendre les problèmes et les approches pour les résoudre. Enfin, vous n'avez pas posé de questions sur les nouvelles sauts impairs dans votre sortie, mais elles sont probablement dues au fait que vous utilisez le PrintStream étant vidé (et donc écrire leur contenu sur le BAOS) en tant que signal pour imprimer le préfixe, par opposition à quelque chose comme voir un retour à la ligne dans le tampon bytes. Si ce flush se produit avant que le saut de ligne ne soit écrit, vous verrez le comportement que vous voyez.

+0

J'ai essayé ceci, mais il semble donner les mêmes résultats. Le préfixe est ajouté uniquement à certains journaux. Je crois que c'est probablement quelque chose à faire avec le tampon. J'ai rendu le 'cont' volatile et j'ai changé le code dans ma boucle pour celui que vous aviez montré, mais il semble toujours faire la même chose. – Garhoogin

+0

@Garhoogin Mis à jour – yshavit