Lock est une interface de niveau supérieur, et toutes ses méthodes sont présentées ci-dessous :
Sa liste de sous-classes est la suivante :
Nous utilisons généralement ReentrantLock pour définir son instances , La relation entre eux est illustrée dans la figure ci-dessous :
PS : Sync signifie verrouillage de synchronisation, FairSync signifie verrouillage équitable et NonfairSync signifie verrouillage injuste.
L'apprentissage de toute compétence commence par son utilisation, nous ne faisons donc pas exception. Jetons d'abord un coup d'œil à l'utilisation de base de ReentrantLock :
public class LockExample { // 创建锁对象 private final ReentrantLock lock = new ReentrantLock(); public void method() { // 加锁操作 lock.lock(); try { // 业务代码...... } finally { // 释放锁 lock.unlock(); } } }
Après la création de ReentrantLock, il y a deux points clés :
Opération de verrouillage : lock()
Opération de verrouillage : unlock()
Beaucoup de gens penseront que (surtout pour les amis novices) , l'implémentation par défaut de ReentrantLock est un verrou équitable, mais ce n'est pas le cas. ReentrantLock est un verrou injuste par défaut (cela est principalement dû à des considérations de performances),
Par exemple, le code suivant :
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 创建锁对象 private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { // 定义线程任务 Runnable runnable = new Runnable() { @Override public void run() { // 加锁 lock.lock(); try { // 打印执行线程的名字 System.out.println("线程:" + Thread.currentThread().getName()); } finally { // 释放锁 lock.unlock(); } } }; // 创建多个线程 for (int i = 0; i < 10; i++) { new Thread(runnable).start(); } } }
. Les résultats d'exécution du programme ci-dessus sont les suivants :
D'après les résultats d'exécution ci-dessus, nous pouvons voir que ReentrantLock est un verrou injuste par défaut. Étant donné que les noms des threads sont incrémentés en fonction de l'ordre dans lequel ils sont créés, s'il s'agit d'un verrou équitable, alors l'exécution des threads devrait augmenter dans l'ordre. Cependant, comme le montrent les résultats ci-dessus, l'exécution et l'impression de. les threads sont dans le désordre. Cette description ReentrantLock est un verrou injuste par défaut.
Il est également très simple de définir ReentrantLock comme un verrou équitable. Il vous suffit de définir un véritable paramètre de construction lors de la création de ReentrantLock Comme le montre le code suivant :
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 创建锁对象(公平锁) private static final ReentrantLock lock = new ReentrantLock(true); public static void main(String[] args) { // 定义线程任务 Runnable runnable = new Runnable() { @Override public void run() { // 加锁 lock.lock(); try { // 打印执行线程的名字 System.out.println("线程:" + Thread.currentThread().getName()); } finally { // 释放锁 lock.unlock(); } } }; // 创建多个线程 for (int i = 0; i < 10; i++) { new Thread(runnable).start(); } } }
Les résultats d'exécution du programme ci-dessus sont les suivants. suit :
Comme le montrent les résultats ci-dessus, lorsque nous définissons explicitement le véritable paramètre de construction pour ReentrantLock, ReentrantLock devient un verrou équitable et l'ordre dans lequel les threads acquièrent les verrous devient ordonné.
En fait, à partir du code source de ReentrantLock, nous pouvons également voir s'il s'agit d'un verrou équitable ou injuste. Une partie du code source de ReentrantLock est implémentée comme suit :
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
Comme on peut le voir sur le. au-dessus du code source, ReentrantLock créera un verrou injuste par défaut Lock, si la valeur du paramètre de construction est explicitement définie sur true lors de sa création, il créera un verrou équitable.
Lorsque vous utilisez ReentrantLock, vous devez vous rappeler de libérer le verrou, sinon le verrou sera occupé tout le temps et les autres threads utilisant le verrou attendront éternellement, nous utilisons donc ReentrantLock Ce faisant, assurez-vous de déverrouiller enfin le verrou, afin de pouvoir être sûr que le verrou sera libéré.
Contre exemple
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 创建锁对象 private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { // 加锁操作 lock.lock(); System.out.println("Hello,ReentrantLock."); // 此处会报异常,导致锁不能正常释放 int number = 1 / 0; // 释放锁 lock.unlock(); System.out.println("锁释放成功!"); } }
Le résultat de l'exécution du programme ci-dessus est le suivant :
À partir des résultats ci-dessus, on peut voir que lorsqu'une exception se produit, le verrou n'est pas libéré normalement, ce qui les autres threads utilisant le verrou seront en permanence dans un état d'attente.
Exemple positif
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 创建锁对象 private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { // 加锁操作 lock.lock(); try { System.out.println("Hello,ReentrantLock."); // 此处会报异常 int number = 1 / 0; } finally { // 释放锁 lock.unlock(); System.out.println("锁释放成功!"); } } }
Les résultats d'exécution du programme ci-dessus sont les suivants :
À partir des résultats ci-dessus, on peut voir que bien qu'une exception se produise dans la méthode, elle n'affecte pas la libérer l'opération du verrou ReentrantLock, afin que les autres threads utilisant ce verrou puissent obtenir et s'exécuter normalement.
Le nombre d'opérations de verrouillage et le nombre d'opérations de déverrouillage doivent correspondre un à un, et un verrou ne peut pas être libéré plusieurs fois, car cela entraînerait le signalement d'une erreur par le programme.
Contre-exemple
Un verrou correspond à deux opérations de déverrouillage, ce qui amène le programme à signaler une erreur et à mettre fin à l'exécution. L'exemple de code est le suivant :
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 创建锁对象 private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { // 加锁操作 lock.lock(); // 第一次释放锁 try { System.out.println("执行业务 1~"); // 业务代码 1...... } finally { // 释放锁 lock.unlock(); System.out.println("锁释锁"); } // 第二次释放锁 try { System.out.println("执行业务 2~"); // 业务代码 2...... } finally { // 释放锁 lock.unlock(); System.out.println("锁释锁"); } // 最后的打印操作 System.out.println("程序执行完成."); } }
Les résultats d'exécution du programme ci-dessus sont les suivants. :
À partir des résultats ci-dessus, vous pouvez voir que lorsque le deuxième déverrouillage est exécuté, le programme signale une erreur et termine l'exécution, ce qui empêche le code après l'exception de s'exécuter normalement.
在使用 ReentrantLock 时,需要注意不要将加锁操作放在 try 代码中,这样会导致未加锁成功就执行了释放锁的操作,从而导致程序执行异常。
反例
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 创建锁对象 private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { try { // 此处异常 int num = 1 / 0; // 加锁操作 lock.lock(); } finally { // 释放锁 lock.unlock(); System.out.println("锁释锁"); } System.out.println("程序执行完成."); } }
以上程序的执行结果如下:
从上述结果可以看出,如果将加锁操作放在 try 代码中,可能会导致两个问题:
未加锁成功就执行了释放锁的操作,从而导致了新的异常;
释放锁的异常会覆盖程序原有的异常,从而增加了排查问题的难度。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!