J'ai toujours pensé que la synchronisation de la méthode run dans une classe Java implémentant Runnable était redondante. J'essaie de comprendre pourquoi les gens font ça:
public class ThreadedClass implements Runnable{
//other stuff
public synchronized void run(){
while(true)
//do some stuff in a thread
}
}
}
Cela semble redondant et inutile car ils obtiennent le verrou de l'objet pour un autre thread. Ou plutôt, ils expliquent qu'un seul thread a accès à la méthode run (). Mais puisque c'est la méthode d'exécution, n'est-ce pas lui-même son propre thread? Par conséquent, seul le système peut accéder à lui-même et ne nécessite pas de mécanisme de verrouillage distinct?
J'ai trouvé une suggestion en ligne selon laquelle, en synchronisant la méthode d'exécution, vous pourriez potentiellement créer une file de threads de facto, par exemple:
public void createThreadQueue(){
ThreadedClass a = new ThreadedClass();
new Thread(a, "First one").start();
new Thread(a, "Second one, waiting on the first one").start();
new Thread(a, "Third one, waiting on the other two...").start();
}
Je ne le ferais jamais personnellement, mais cela pose la question de savoir pourquoi quelqu'un synchroniserait la méthode d'exécution.Des idées pourquoi ou pourquoi ne pas synchroniser la méthode d'exécution?
La synchronisation de la méthode run(
) d'une Runnable
est totalement inutile à moins que ne souhaite partager la Runnable
entre plusieurs threads et pour séquencier l'exécution de ces threads. Ce qui est fondamentalement une contradiction dans les termes.
En théorie, il existe un autre scénario beaucoup plus compliqué dans lequel vous pouvez synchroniser la méthode run()
, qui implique à nouveau le partage de Runnable
entre plusieurs threads, mais utilise également les fonctions wait()
et notify()
. Je ne l'ai jamais rencontré depuis plus de 21 ans en Java.
Il y a un avantage à utiliser synchronized void blah()
sur void blah() { synchronized(this) {
et c'est le bytecode résultant qui sera plus court d'un octet, car la synchronisation fera partie de la signature de la méthode plutôt que d'une opération. Cela peut influer sur les chances d'insérer la méthode par le compilateur JIT. Autre que cela il n'y a pas de différence.
La meilleure option consiste à utiliser une private final Object lock = new Object()
interne pour empêcher toute personne de verrouiller potentiellement votre moniteur. Le même résultat est obtenu sans les inconvénients du verrouillage extérieur maléfique. Vous avez cet octet supplémentaire, mais cela fait rarement une différence.
Donc, je dirais non, n'utilisez pas le mot clé synchronized
dans la signature. Au lieu de cela, utilisez quelque chose comme
public class ThreadedClass implements Runnable{
private final Object lock = new Object();
public void run(){
synchronized(lock) {
while(true)
//do some stuff in a thread
}
}
}
}
Modifier en réponse au commentaire:
Considérez ce que la synchronisation fait: cela empêche les autres threads d'entrer le même bloc de code. Alors, imaginez que vous ayez une classe comme celle ci-dessous. Disons que la taille actuelle est de 10. Quelqu'un essaie de faire un ajout et force le redimensionnement du tableau de sauvegarde. Alors qu'ils sont en train de redimensionner le tableau, quelqu'un appelle une makeExactSize(5)
sur un autre thread. Tout à coup, vous essayez d'accéder à data[6]
et il vous bombarde. La synchronisation est censée empêcher cela. Dans les programmes multithread, vous avez besoin simplement de la synchronisation NEED.
class Stack {
int[] data = new int[10];
int pos = 0;
void add(int inc) {
if(pos == data.length) {
int[] tmp = new int[pos*2];
for(int i = 0; i < pos; i++) tmp[i] = data[i];
data = tmp;
}
data[pos++] = inc;
}
int remove() {
return data[pos--];
}
void makeExactSize(int size) {
int[] tmp = new int[size];
for(int i = 0; i < size; i++) tmp[i] = data[i];
data = tmp;
}
}
Pourquoi? Un minimum de sécurité supplémentaire et je ne vois pas de scénario plausible où cela ferait une différence.
Pourquoi pas? Ce n'est pas standard. Si vous codez en équipe, lorsqu'un autre membre voit votre run
synchronisée, il perdra probablement 30 minutes à essayer de comprendre ce qui est si spécial avec votre run
ou avec la structure que vous utilisez pour exécuter les Runnable
.
D'après mon expérience, il n'est pas utile d'ajouter le mot clé "synchronized" à la méthode run (). Si nous avons besoin de synchroniser plusieurs threads ou si nous avons besoin d'une file d'attente sécurisée pour les threads, nous pouvons utiliser des composants plus appropriés, tels que ConcurrentLinkedQueue.
Eh bien, vous pouvez théoriquement appeler la méthode run elle-même sans problème (après tout, elle est publique). Mais cela ne signifie pas qu'il faille le faire. Donc, fondamentalement, il n’ya aucune raison de le faire, mis à part l’ajout d’une surcharge négligeable au thread appelant run (). Eh bien, sauf si vous utilisez l'instance plusieurs fois en appelant new Thread
- bien que je sois a) pas sûr que ce soit légal avec l'API de threading et b) semble complètement inutile.
De plus, votre createThreadQueue
ne fonctionne pas. synchronized
sur une méthode non statique se synchronise sur l'objet d'instance (c'est-à-dire this
), de sorte que les trois threads s'exécutent en parallèle.
Parcourez les commentaires de code et supprimez le commentaire, puis exécutez les différents blocs pour voir clairement la différence. Notez que la synchronisation n'aura de différence que si la même instance exécutable est utilisée. Si chaque thread démarré obtient un nouveau exécutable, cela ne fera aucune différence.
class Kat{
public static void main(String... args){
Thread t1;
// MyUsualRunnable is usual stuff, only this will allow concurrency
MyUsualRunnable m0 = new MyUsualRunnable();
for(int i = 0; i < 5; i++){
t1 = new Thread(m0);//*imp* here all threads created are passed the same runnable instance
t1.start();
}
// run() method is synchronized , concurrency killed
// uncomment below block and run to see the difference
MySynchRunnable1 m1 = new MySynchRunnable1();
for(int i = 0; i < 5; i++){
t1 = new Thread(m1);//*imp* here all threads created are passed the same runnable instance, m1
// if new insances of runnable above were created for each loop then synchronizing will have no effect
t1.start();
}
// run() method has synchronized block which lock on runnable instance , concurrency killed
// uncomment below block and run to see the difference
/*
MySynchRunnable2 m2 = new MySynchRunnable2();
for(int i = 0; i < 5; i++){
// if new insances of runnable above were created for each loop then synchronizing will have no effect
t1 = new Thread(m2);//*imp* here all threads created are passed the same runnable instance, m2
t1.start();
}*/
}
}
class MyUsualRunnable implements Runnable{
@Override
public void run(){
try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
}
class MySynchRunnable1 implements Runnable{
// this is implicit synchronization
//on the runnable instance as the run()
// method is synchronized
@Override
public synchronized void run(){
try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
}
class MySynchRunnable2 implements Runnable{
// this is explicit synchronization
//on the runnable instance
//inside the synchronized block
// MySynchRunnable2 is totally equivalent to MySynchRunnable1
// usually we never synchronize on this or synchronize the run() method
@Override
public void run(){
synchronized(this){
try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
}
}