2
votes

La classe SingleTon n'est pas synchronisée et renvoie plusieurs instances

Il y a un problème avec mon code ci-dessous. J'obtiens plusieurs instances de la même classe singleton lorsque je l'appelle à partir de plusieurs threads du même processus. Je m'attends à ce que le programme n'imprime "Création de l'instance" qu'une seule fois, mais il imprime 3 fois (c'est-à-dire par thread).

test
Thread Thread-0 is starting
Thread Thread-2 is starting
Thread Thread-1 is starting
Creating instance
Creating instance
Creating instance

Sortie:

package SingleTon;

class SingleTon implements Runnable {
    String name;
    int salary;
    private static SingleTon singleTonInstance;

    SingleTon() {

    }

    public static SingleTon getInstance() {
        if (singleTonInstance == null) {
            synchronized (SingleTon.class) {
                System.out.println("Creating instance");
                singleTonInstance = new SingleTon();
            }
        }
        return singleTonInstance;
    }

    @Override
    public void run() {
        System.out.format("Thread %s is starting\n",Thread.currentThread().getName());
        getInstance();
    }

}

package SingleTon;

public class SingleTonDemo {

    public static void main(String[] args) {
        System.out.println("test");
        SingleTon t = new SingleTon();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
    }

}


3 commentaires

@ElliottFrisch Link est (a) obsolète et (b) non pertinent. Il n'y a pas de verrouillage vérifié ici,


Sauf qu'il s'agit d'un verrouillage à contrôle unique (donc insuffisant).


J'ai posté une réponse


5 Réponses :


5
votes

Vous devez modifier ce qui suit:

1) Rendez votre constructeur par défaut privé. Tel qu'il est écrit, il est privé du package et peut être appelé par d'autres classes du même package.

public static SingleTon getInstance() {
    return singleTonInstance;
}

Une fois que vous avez fait cela, votre SingleTonDemo class ne pourra plus appeler le constructeur et sera obligée d'utiliser la méthode getInstance () . Actuellement, chaque appel au constructeur crée une nouvelle instance.

2) Synchronisez toute la méthode getInstance () . Comme écrit, 2 threads séparés peuvent obtenir une valeur nulle pour singleTonInstance , et ainsi chaque thread créerait une nouvelle instance.

private static SingleTon singleTonInstance = new SingleTon();

2a) Une alternative L'approche pour s'assurer qu'il n'y a qu'une seule instance de la classe consiste à utiliser l'initialisation statique. Dans la déclaration de la variable statique, vous créez l'objet:

    public static SingleTon getInstance() {

La JVM veillera à ce que cela ne soit appelé qu'un seul. Cela améliore également votre méthode getInstance comme vous la changeriez en:

private SingleTon() {
}

L'avantage de cette approche est que non seulement la méthode getInstance est plus simple, mais elle n'entraîne pas non plus de surcharge d'être synchronisé.


7 commentaires

Merci @EJK pour votre réponse. Mais je voudrais comprendre plus loin, oui je vais le rendre privé d'accord. Mais disons que deux threads entrent dans le bloc if et qu'il y a synchronisé (SingleTon.class), donc deux threads ne doivent pas entrer pour créer l'instance, n'est-ce pas?


Si vous synchronisez la méthode, le thread appelant doit acquérir le verrou au niveau de la classe avant de pouvoir effectuer l'appel. Cela garantit que le premier thread sera en mesure de créer l'instance initiale avant que tout thread suivant puisse entrer dans la méthode. Les threads suivants verront alors une instance non nulle et donc le conditionnel empêche les autres threads de créer plus d'instances.


Cela a fonctionné de cette façon, comme vous l'avez suggéré. 1) constructeur privé et 2) SingleTon t = SingleTon.getInstance (); dans ma classe de démonstration. Je n'ai rien changé à la méthode singleton.


Je suis sûr que cela fonctionnera 99,99% du temps si vous sautez l'étape 2, mais dans un programme multithread de longue durée, il y a la possibilité que finalement le timing s'aligne juste (ou mal pour vous) ce qui déclencherait la 2ème création . L'étape 2 rend votre code 100% à l'épreuve des balles.


Je vois, encore une chose. une fois que le thread est à l'intérieur de la méthode signifie qu'il n'obtiendra pas la valeur qui est déjà écrite par le thread précédent? Désolé je les apprends


Cette instance statique peut être lue par tous les threads. Cependant, lorsque vous synchronisez la méthode getInstance (qui est la seule et unique méthode qui peut créer l'instance), cela garantit qu'un seul thread peut être dans la méthode à la fois, garantissant ainsi qu'une seule instance est créée. Je ne sais pas si cela répond à votre question ou non.


@techi, dans quelle méthode? savez-vous ce que fait synchronized ? Lorsque vous utilisez un bloc synchronisé , Java utilise en interne un moniteur également appelé verrouillage du moniteur ou verrouillage intrinsèque , pour assurer la synchronisation. Ces moniteurs sont liés à un objet, ainsi tous les blocs synchronisés du même objet ne peuvent avoir qu'un UN thread qui les exécute en même temps. Donc, si une valeur est déjà écrite par le thread précédent, le thread suivant y aura accès.



2
votes

Vous créez en fait une instance de votre classe SingleTon en dehors de la classe. Cela ne devrait pas être fait lors de l'utilisation du modèle singleton, car seule une seule instance de la classe devrait pouvoir être créée.

Pour le problème de synchronisation:

Si les trois threads entrent l'instruction if dans votre méthode getInstance , chaque thread créera une instance (comme l'instruction if elle-même l'est non couvert par le bloc synchronisé). Une façon de résoudre ce problème est de synchroniser la méthode getInstance , c'est-à-dire en ajoutant le modificateur synchronized . Dans ce cas, la logique fetch-and-create-if-not-exists dans votre méthode getInstance est exécutée comme un bloc atomique.


3 commentaires

Merci pour votre réponse. Mais j'ai synchronisé (SingleTon.class) {, alors pourquoi n'est-il pas synchronisé?


Votre bloc synchronisé ne démarre que dans l'instruction if. Prenons cet exemple possible avec deux threads, A et B. Thread A vérifie la variable statique: n'existe pas. Le thread B vérifie la variable statique: n'existe pas. A crée l'instance et la stocke. Ensuite (à cause de la synchronisation) B crée l'instance et la stocke. Le fait est que les threads peuvent s'arrêter avant le bloc synchronisé. Le bloc sera alors exécuté par les deux threads sans autre vérification.


M'a aidé à comprendre en profondeur! Excellent



-1
votes

Tout d'abord, votre constructeur de singleton DOIT ÊTRE privé . Deuxièmement, utilisez un objet comme mutex pour éviter les collisions.

test
Creating instance
Thread Thread-0 is starting
Thread Thread-2 is starting
Thread Thread-1 is starting

Maintenant, appelez la méthode getInstance () dans votre main comme ceci:

public class SingletonDemo
{
    public static void main(String[] args) {
        System.out.println("test");
        RunnableSingleton t = RunnableSingleton.getInstance();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
    }
}

Voici le sortie de l'implémentation:

public class RunnableSingleton implements Runnable
{
    private static volatile RunnableSingleton instance;
    private static final Object mutex = new Object();

    private RunnableSingleton() {}

    public static RunnableSingleton getInstance()
    {
        RunnableSingleton result = instance;
        if (result == null) {
            synchronized (mutex) {
                result = instance;
                if (result == null) {
                    System.out.println("Creating instance");
                    instance = result = new RunnableSingleton();
                }
            }
        }

        return result;
    }

    @Override
    public void run()
    {
        System.out.format("Thread %s is starting\n", Thread.currentThread().getName());
        getInstance();
    }
}


3 commentaires

Merci d'avoir pris le temps d'écrire un programme pour moi !. Une question pour comprendre un objet final statique privé mutex = new Object (); donc chaque objet doit acquérir le verrou sur cet objet avant d'entrer pour créer l'objet. Je suppose que vous avez implémenté un verrouillage double-vérifié pour un singleton?


Premièrement, il n'est pas possible que deux invocations de méthodes synchronisées sur le même objet s'entrelacent. Lorsqu'un thread exécute une méthode synchronisée pour un objet, tous les autres threads qui invoquent des méthodes synchronisées pour le même objet bloquent (suspendent l'exécution) jusqu'à ce que le premier thread soit terminé avec l'objet. S, vous avez 1 variables non entrelacées. Vous souhaitez donc y accéder à partir de différents threads en même temps. vous devez définir le verrou non pas sur la classe d'objet elle-même mais sur la classe Object. C'est la définition de base du singleton.


Je suis d'accord. votre code fonctionne sans aucun problème. Je n'ai pas voté contre. celui qui l'a fait, veuillez expliquer. Mais je ne peux accepter qu'une seule réponse.



0
votes

La Réponse d'EJK semble correcte.

Voici une approche alternative plus simple.

Enum

De manière générale, la meilleure façon d'implémenter un Singleton en Java est de créer une énumération. La fonction enum de Java est beaucoup plus puissante et flexible que dans la plupart des plates-formes; voir Tutoriel .

Un enum en Java est en fait une sous-classe de Enum , avec le compilateur / JVM qui gère cela derrière les rideaux. Lorsque votre classe enum est chargée, la JVM instancie automatiquement un objet pour chacun de vos noms d'énumération. Dans le cas d'un singleton, bien sûr, nous n'avons qu'un seul nom d'énumération. Généralement, ce nom est INSTANCE mais peut être tout ce que vous désirez.

Comme une énumération en Java est en fait une classe, vous pouvez définir un constructeur et lui transmettre des objets. Dans notre exemple ci-dessous, nous transmettons une valeur initiale pour les membres name et salaire que vous avez utilisés dans votre question.

De même, en tant que classe, un enum en Java peut avoir des méthodes. Dans notre exemple ci-dessous, nous avons ajouté une méthode run pour implémenter Runnable , comme vous l'avez fait dans votre question.

Dans l'exemple ici, nous avons ajouté a Valeur UUID comme identifiant comme une autre façon pour vous de voir qu'en effet nous n'avons qu'une seule instance de notre singleton.

package work.basil.example;

import java.time.Instant;
import java.util.UUID;

public enum SingletonDemo implements Runnable {
    INSTANCE( "Alice" , 10_000 );

    String name;
    Integer salary;
    UUID uuid;

    SingletonDemo ( String name , Integer salary ) {
        System.out.println( "DEBUG - Constructing `SingletonDemo` enum at " + Instant.now() );
        this.name = name;
        this.salary = salary;
        this.uuid = UUID.randomUUID();
    }

    @Override
    public void run () {
        // CAUTION: Be sure you make this method thread-safe!
        System.out.format( "Thread %s is starting. uuid: %s\n" , Thread.currentThread().getName() ,this.uuid.toString() ); // Calling getters as I did here may not be thread-safe without further precautions (synchronization, etc.).
    }

    public static void main ( String[] args ) {
        Thread t1 = new Thread( SingletonDemo.INSTANCE );
        Thread t2 = new Thread( SingletonDemo.INSTANCE );
        Thread t3 = new Thread( SingletonDemo.INSTANCE );
        t1.start();
        t2.start();
        t3.start();
    }
}

Lors de l'exécution.

DEBUG - Construction de l'énumération SingletonDemo à 2019-01-30T00: 48: 46.614462Z

Thread Thread-0 démarre. uuid: a2825344-fb15-45d1-a6e4-239fc3bdf3c5

Thread Thread-2 démarre. uuid: a2825344-fb15-45d1-a6e4-239fc3bdf3c5

Thread Thread-1 démarre. uuid: a2825344-fb15-45d1-a6e4-239fc3bdf3c5


3 commentaires

Impressionnant! Je connais les bases de l'énumération mais je ne l'ai pas beaucoup utilisé en Java. Utilisé uniquement lorsqu'il est nécessaire de définir des types d'énumération personnalisés et non dans ce genre d'objet. Puisqu'il s'agit d'une classe enum, est-ce que d'autres opérations telles que sérialisable, les opérations de clonage fonctionneront en temps réel? désolé ce n'est pas pertinent pour ma question dans ce post


Je ne connais aucune restriction à une énumération. C'est une classe régulière à presque tous égards. La seule raison pour laquelle je sais de ne pas utiliser cette approche pour votre singleton est si vous devez hériter d'une classe. Une énumération en Java est déjà une sous-classe de la classe Enum , et en Java, nous ne pouvons avoir qu'une seule ligne d'héritage. Cependant, vous pouvez implémenter des interfaces, comme nous avons implémenté Runnable ici. Recherchez Stack Overflow et Internet pour en savoir plus, car l'utilisation d'une énumération pour singleton en Java est assez courante.


Ok merci, @Basil Bourque. Appréciez votre aide et votre temps



0
votes
private SingleTon() {

}

0 commentaires