Maison  >  Article  >  Java  >  Exemple d'analyse des problèmes de sécurité des mots clés et des threads synchronisés en Java

Exemple d'analyse des problèmes de sécurité des mots clés et des threads synchronisés en Java

高洛峰
高洛峰original
2017-01-05 14:47:481079parcourir

Tout d'abord, passons en revue l'utilisation de base de synchronisé :

bloc de code synchronisé, le code modifié devient un bloc d'instructions synchronisé, et sa portée est d'appeler l'objet de ce bloc de code. Nous utilisons le synchronisé. mot-clé Lorsque vous le pouvez, réduisez autant que possible la portée du segment de code. Si vous pouvez ajouter une synchronisation au segment de code, n'ajoutez pas de synchronisation à l'ensemble de la méthode. C'est ce qu'on appelle réduire la granularité du verrou et rendre le code plus concurrent.

méthode synchronisée, la méthode modifiée devient une méthode synchronisée, sa portée est la méthode entière et l'objet cible est l'objet qui appelle cette méthode.

La méthode statique synchronisée modifie une méthode statique, sa portée est la méthode statique entière et sa cible est tous les objets de cette classe.

classe synchronisée, sa portée est la partie de synchronisé(className.class) entre parenthèses après Synchronisé, et les objets sur lesquels elle agit sont tous des objets de cette classe.

synchronized() () est l'objet verrouillé. synchronisé(this) verrouille uniquement l'objet lui-même. La méthode synchronisée appelée par différents objets de la même classe ne sera pas verrouillée, et synchronisé(className .class) implémente. la fonction de verrouillage global.Tous les objets de cette classe qui appellent cette méthode sont affectés par le verrou.De plus, un objet spécifique peut être ajouté dans () pour verrouiller un objet spécifique.

synchronized (object) {
 //在同步代码块中对对象进行操作 
}

Mot clé synchronisé et sécurité des threads
Je pensais que l'utilisation du mot clé synchronisé pour envelopper le code rendrait la synchronisation des threads sûre. Je l'ai testé. Je me suis rendu compte que c'était complètement faux. synchronisé doit être utilisé correctement pour être véritablement thread-safe. . . Bien que je connaisse cette façon d’écrire, j’ai toujours pensé que j’utilisais la mauvaise méthode par paresse.
Il semble que les fondations ne soient pas encore posées. Encore faut-il revoir et renforcer ! Il est impardonnable de commettre ce genre d'erreur au travail. Il faut savoir que partout où le mot-clé synchronisé est utilisé, les données sont sensibles ! Transpirez beaucoup. . .
Publiez d'abord le code :

package com; 
  
public class ThreadTest { 
 public static void main(String[] args) { 
  MyThread m1 = new MyThread(1); 
  MyThread m2 = new MyThread(2); 
  m1.start(); 
  m2.start(); 
 } 
} 
  
final class MyThread extends Thread { 
 private int val; 
  
 public MyThread(int v) { 
  val = v; 
 } 
 //这种做法其实是非线程安全的 
 public synchronized void print1(int v) { 
  for (int i = 0; i < 100; i++) { 
   System.out.print(v); 
  } 
 } 
  
 public void print2(int v) { 
  //线程安全 
  synchronized (MyThread.class) { 
   for (int i = 0; i < 100; i++) { 
    System.out.print(v); 
   } 
  } 
 } 
  
 public void run() { 
  print1(val); 
  // print2(val); 
 } 
}

Juste pour être paresseux, transpirez. . . Les programmeurs sont toujours paresseux. Écrivez le moins possible. J'ai écrit MyThread en tant que classe interne finale anonyme pour faciliter les appels. Il utilise le thread d'héritage le plus direct pour implémenter une classe de thread et définit la méthode run() qui doit être exécutée.
Annotez d’abord la méthode print2() pour voir quel est le résultat de print1(). print1() est une méthode définie à l'aide du mot-clé synchronisé. J'ai toujours pensé que cela pourrait également assurer la sécurité des threads. Comme tout le monde le sait, j'avais tort.
Exécutons directement la méthode main(). Le résultat de l'impression de la console est le suivant :

1212111121212121212121212121212121212121222222212121212。。。

est une série de résultats imprimés de manière croisée de 1 et 2. Dans ma méthode principale, j'exécute d'abord m1 puis m2, ce qui montre que la synchronisation des threads n'est pas réalisée !

MyThread m1 = new MyThread(1); 
MyThread m2 = new MyThread(2); 
m1.start(); 
m2.start();

Ensuite, nous commentons print1() dans la méthode run et exécutons print2();
La console s'imprime comme suit :

11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222

Le thread est effectivement sécurisé, J'ai toujours pensé que je connaissais cette façon d'écrire, mais comme cette façon d'écrire comporte un peu plus de code, je n'y ai pas beaucoup réfléchi et j'ai réalisé cette erreur aujourd'hui. Il semble que parfois cela vaut la peine de ne pas être paresseux. Poser de bonnes bases est important. Corriger une erreur de longue date.

Jetons un coup d’œil aux raisons spécifiques.

Le mot-clé synchronisé peut être utilisé comme modificateur d'une fonction ou comme instruction au sein d'une fonction, communément appelée méthodes de synchronisation et blocs d'instructions de synchronisation. S'il est classé plus en détail, synchronisé peut agir sur des variables d'instance, des références d'objet, des fonctions statiques et des littéraux de classe (littéraux de nom de classe).
Avant d'élaborer davantage, nous devons clarifier quelques points :
A. Que le mot-clé synchronisé soit ajouté à une méthode ou à un objet, le verrou qu'il acquiert est un objet, plutôt que de traiter un morceau de code ou une fonction comme un verrou - et la méthode synchronisée est susceptible d'être accessible par des objets provenant d'autres fils.
B. Chaque objet n’est associé qu’à un seul verrou.
C. La réalisation de la synchronisation nécessite beaucoup de temps système et peut même provoquer un blocage. Essayez donc d'éviter un contrôle de synchronisation inutile.
Discutons ensuite de l'impact de la synchronisation à différents endroits sur le code :
Supposons que P1 et P2 soient des objets différents de la même classe. Cette classe définit des blocs de synchronisation ou des méthodes de synchronisation dans les situations suivantes, P1, P2. je peux tous les appeler.
1. Lorsque synchronisé est utilisé comme modificateur de fonction, l'exemple de code est le suivant :

Public synchronized void methodAAA() 
{ 
//…. 
}

Il s'agit de la méthode de synchronisation. Alors, quel objet est synchronisé verrouillé à ce moment-là ? Ce qu'il verrouille, c'est l'objet qui appelle cette méthode synchronisée. C'est-à-dire que lorsqu'un objet P1 exécute cette méthode de synchronisation dans différents threads, une exclusion mutuelle se formera entre eux pour obtenir un effet de synchronisation. Cependant, un autre objet P2 généré par la Classe à laquelle appartient cet objet peut appeler arbitrairement cette méthode avec le mot-clé synchronisé.
L'exemple de code ci-dessus est équivalent au code suivant :

public void methodAAA() 
{ 
synchronized (this) // (1) 
{ 
//….. 
} 
}

À quoi fait référence ce point (1) ? Il fait référence à l'objet qui appelle cette méthode, tel que P1. On peut voir que l'essence de la méthode de synchronisation est d'appliquer la synchronisation à la référence d'objet. ——Seul le thread qui a obtenu le verrou d'objet P1 peut appeler la méthode de synchronisation de P1. En ce qui concerne P2, le verrou P1 n'a rien à voir avec cela. Le programme peut également se débarrasser du contrôle du mécanisme de synchronisation. dans cette situation, ce qui entraîne une confusion des données !

2. Bloc synchronisé, l'exemple de code est le suivant :

public void method3(SomeObject so) 
{ 
synchronized(so) 
{ 
//….. 
} 
}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable 
{ 
private byte[] lock = new byte[0]; // 特殊的instance变量 
Public void methodA() 
{ 
synchronized(lock) { //… } 
} 
//….. 
}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:

Class Foo 
{ 
public synchronized static void methodAAA() // 同步的static 函数 
{ 
//…. 
} 
public void methodBBB() 
{ 
synchronized(Foo.class) // class literal(类名称字面常量) 
} 
}

代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
小结如下:
搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。
还有一些技巧可以让我们对共享资源的同步访问更加安全:
1.定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。
2.如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。

总结一些synchronized注意事项:

当两个并发线程访问同一个对象中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。两个线程间是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。(两个线程使用的是同一个对象)

当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞(同上,两个线程使用的是同一个对象)。

更多实例解析Java中的synchronized关键字与线程安全问题相关文章请关注PHP中文网!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn