Si j'ai 2 méthodes synchronisées dans la même classe, mais que chacune accède à des variables différentes, 2 threads peuvent-ils accéder à ces 2 méthodes en même temps? Le verrou se produit-il sur l'objet ou est-il aussi spécifique que les variables de la méthode synchronisée?
Exemple:
class X {
private int a;
private int b;
public synchronized void addA(){
a++;
}
public synchronized void addB(){
b++;
}
}
Deux threads peuvent-ils accéder à la même instance de classe X en exécutant x.addA(
) et x.addB()
en même temps?
Si vous déclarez la méthode comme synchronisée (comme vous le faites en tapant public synchronized void addA()
), vous synchronisez sur le entier = object, ainsi deux threads accédant à une variable différente de ce même objet se bloqueraient quand même.
Si vous souhaitez synchroniser uniquement une variable à la fois, afin que deux threads ne se bloquent pas lors de l'accès à des variables différentes, vous devez effectuer la synchronisation séparément dans des blocs synchronized ()
. Si a
et b
étaient des références d'objet, vous utiliseriez:
public void addA() {
synchronized( a ) {
a++;
}
}
public void addB() {
synchronized( b ) {
b++;
}
}
Mais comme ce sont des primitives, vous ne pouvez pas faire cela.
Je vous suggère d'utiliser AtomicInteger à la place:
import Java.util.concurrent.atomic.AtomicInteger;
class X {
AtomicInteger a;
AtomicInteger b;
public void addA(){
a.incrementAndGet();
}
public void addB(){
b.incrementAndGet();
}
}
Synchronisé sur la déclaration de la méthode est un sucre syntaxique pour ceci:
public void addA() {
synchronized (this) {
a++;
}
}
Sur une méthode statique, c'est un sucre syntaxique pour ceci:
ClassA {
public static void addA() {
synchronized(ClassA.class) {
a++;
}
}
Je pense que si les Java concepteurs savaient alors ce que l’on comprend maintenant en ce qui concerne la synchronisation, ils n’auraient pas ajouté le sucre syntaxique, car cela aboutissait le plus souvent à une mauvaise implémentation de la simultanéité.
À partir de "The Java ™ Tutorials" sur les méthodes synchronisées :
Tout d'abord, 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 bloc d'objet (suspendre l'exécution) jusqu'à la fin du premier thread avec l'objet.
Depuis "Les tutoriels Java ™" sur blocs synchronisés :
Les instructions synchronisées sont également utiles pour améliorer la simultanéité avec une synchronisation à granularité fine. Supposons, par exemple, que la classe MsLunch comporte deux champs d'instance, c1 et c2, qui ne sont jamais utilisés ensemble. Toutes les mises à jour de ces champs doivent être synchronisées , mais rien n'empêche d'empêcher une mise à jour de c1 d'être imbriquée dans une mise à jour de c2 - et cela réduit concurrence en créant des blocages inutiles. Au lieu d'utiliser des méthodes synchronisées ou d'utiliser le verrou associé, nous créons deux objets uniquement pour fournir des verrous.
(Mon accentuation)
Supposons que vous avez 2 variables non-entrelacées . Donc, vous voulez accéder à chacun d'eux à partir de différents threads en même temps. Vous devez définir le verrou non sur la classe d'objet elle-même, mais sur la classe Object comme ci-dessous (exemple du deuxième lien Oracle):
public class MsLunch {
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1() {
synchronized(lock1) {
c1++;
}
}
public void inc2() {
synchronized(lock2) {
c2++;
}
}
}
Le verrou accédé est sur l'objet, pas sur la méthode. Les variables auxquelles on accède dans la méthode sont sans importance.
L'ajout de "synchronized" à la méthode signifie que le thread qui exécute le code doit acquérir le verrou sur l'objet avant de continuer. L'ajout de "static synchronized" signifie que le thread qui exécute le code doit acquérir le verrou sur l'objet de classe avant de continuer. Sinon, vous pouvez envelopper le code dans un bloc comme celui-ci:
public void addA() {
synchronized(this) {
a++;
}
}
afin que vous puissiez spécifier l'objet dont le verrou doit être acquis.
Si vous souhaitez éviter le verrouillage sur l'objet contenant, vous pouvez choisir entre:
De la documentation Oracle lien
Rendre les méthodes synchronisées a deux effets:
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 bloc d'objet (suspendre l'exécution) jusqu'à la fin du premier thread avec l'objet.
Deuxièmement, quand une méthode synchronisée se termine, elle établit automatiquement une relation passe-avant avec tout appel ultérieur d'une méthode synchronisée pour le même objet. Cela garantit que les modifications apportées à l'état de l'objet sont visibles par tous les threads.
Consultez cette documentation page pour comprendre les verrous intrinsèques et le comportement des verrous.
Ceci répondra à votre question: sur le même objet x, vous ne pouvez pas appeler x.addA () et x.addB () simultanément. L'exécution des méthodes synchronisées est en cours.
Vous pouvez faire quelque chose comme ce qui suit. Dans ce cas, vous utilisez le verrou sur a et b pour synchroniser au lieu du verrou sur "this". Nous ne pouvons pas utiliser int parce que les valeurs primitives n'ont pas de verrou, nous utilisons donc Integer.
class x{
private Integer a;
private Integer b;
public void addA(){
synchronized(a) {
a++;
}
}
public synchronized void addB(){
synchronized(b) {
b++;
}
}
}
Cet exemple (bien que pas joli) peut fournir plus d'informations sur le mécanisme de verrouillage. Si incrementA est synchronisé, et incrementB est non synchronisé, alors incrementB sera exécuté dès que possible, mais si incrementB est aussi synchronisé alors il faut 'attendre' que incrementA se termine avant incrementB peut faire son travail.
Les deux méthodes sont appelées sur une seule instance - objet. Dans cet exemple, il s'agit de: job et les threads "en concurrence" sont aThread et principal .
Essayez avec 'synchronized' dans incrementB et sans lui, vous obtiendrez des résultats différents.If incrementB est 'synchronisé' aussi alors il doit attendre incrementA () pour finir. Exécuter plusieurs fois chaque variante.
class LockTest implements Runnable {
int a = 0;
int b = 0;
public synchronized void incrementA() {
for (int i = 0; i < 100; i++) {
this.a++;
System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a);
}
}
// Try with 'synchronized' and without it and you will see different results
// if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish
// public void incrementB() {
public synchronized void incrementB() {
this.b++;
System.out.println("*************** incrementB ********************");
System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b);
System.out.println("*************** incrementB ********************");
}
@Override
public void run() {
incrementA();
System.out.println("************ incrementA completed *************");
}
}
class LockTestMain {
public static void main(String[] args) throws InterruptedException {
LockTest job = new LockTest();
Thread aThread = new Thread(job);
aThread.setName("aThread");
aThread.start();
Thread.sleep(1);
System.out.println("*************** 'main' calling metod: incrementB **********************");
job.incrementB();
}
}
Oui, elle bloquera l'autre méthode car la méthode synchronisée s'applique à l'objet de classe WHOLE comme indiqué .... mais de toute façon, elle bloquera l'exécution de l'autre thread ONLY = tout en effectuant la somme, quelle que soit la méthode addA ou addB dans laquelle il entre, car une fois terminé, l'un des threads FREE l'objet et l'autre thread accéderont à l'autre méthode, et ainsi de suite travail.
Je veux dire que le "synchronisé" est fait précisément pour empêcher l'autre thread d'accéder à un autre pendant l'exécution d'un code spécifique. SO FINALEMENT, CE CODE FONCTIONNERA À LA FIN.
Remarque finale, s’il existe des variables "a" et "b", et pas seulement une variable unique "a" ou un autre nom, il n’est pas nécessaire de synchroniser cette méthode car il est parfaitement sûr d’accéder à une autre variable (Other memory emplacement).
class X {
private int a;
private int b;
public void addA(){
a++;
}
public void addB(){
b++;
}}
Travaillera aussi
Si vous avez des méthodes qui ne sont pas synchronisées et qui accèdent aux variables d'instance et les modifient. Dans votre exemple:
private int a;
private int b;
un nombre quelconque de threads peuvent accéder à ces méthodes non synchronisées en même temps lorsqu'un autre thread est dans la méthode synchronisée du même objet et peut modifier les variables d'instance. Par exemple: -
public void changeState() {
a++;
b++;
}
Vous devez éviter le scénario selon lequel des méthodes non synchronisées accèdent aux variables d'instance et les modifient, sinon l'utilisation de méthodes synchronisées n'a pas sa raison d'être.
Dans le scénario ci-dessous: -
class X {
private int a;
private int b;
public synchronized void addA(){
a++;
}
public synchronized void addB(){
b++;
}
public void changeState() {
a++;
b++;
}
}
Un seul des threads peut figurer dans la méthode addA ou addB, mais simultanément, un nombre quelconque de threads peuvent entrer dans la méthode changeState. Deux threads ne peuvent pas entrer addA et addB en même temps (en raison du verrouillage au niveau de l'objet), mais un nombre quelconque de threads peuvent entrer simultanément dans changeState.
Dans la synchronisation Java, si un thread souhaite entrer dans une méthode de synchronisation, il obtiendra un verrou sur toutes les méthodes synchronisées de cet objet, et pas seulement sur une méthode synchronisée utilisée par le thread. Ainsi, un thread exécutant addA () acquerra un verrou sur addA () et addB () car les deux sont synchronisés. Par conséquent, les autres threads avec le même objet ne peuvent pas exécuter addB ().
Cela pourrait ne pas fonctionner car la boxe et la sélection automatique d'Integer à int et vice-versa dépendent de la machine virtuelle Java et il est fort probable que deux numéros différents puissent être hachés à la même adresse s'ils sont compris entre -128 et 127.