Recommandations d'apprentissage gratuites : Tutoriel de base Java
Serrures de verrouillage et problèmes producteur-consommateur
Verrouillage synchronisé traditionnel
Implémentez un exemple de vente de billets de base :
/* 真正的多线程开发,公司中的开发,降低耦合性 线程就是一个单独的资源类,没有任何附属的操作 1.属性,方法 * */public class SaleTicketDemo1 { public static void main(String[] args) { //并发,多个线程操作同一个资源类,把资源类丢入线程 Ticket ticket=new Ticket(); //Runnable借口是一个FunationalInterface函数式接口,接口可以new,jdk1.8以后,lamda表达式()->{代码} new Thread(()->{ for(int i=0;i{ for(int i=0;i0){ System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number); } }}
Notez que les expressions lambda sont utilisées ici Pour une description détaillée des expressions lambda, voir Bases de Java - Expressions Lambda
Il s'agit d'utiliser la synchronisation traditionnelle pour obtenir la concurrence
L'essence de la synchronisation réside dans les files d'attente et les verrous . C'est comme faire la queue à la cafétéria. S’il n’y a pas de file d’attente, ce sera chaotique. Ce n'est que lorsque le service rendu à une personne est terminé que l'autre personne peut recevoir le service.
Lock
Comme mentionné précédemment, la JVM fournit le mot-clé synchronisé pour obtenir un accès synchrone aux variables et utilise wait et notify pour implémenter la communication inter-thread. Après jdk1.5, JAVA fournit la classe Lock pour implémenter les mêmes fonctions que synchronisées, et fournit également une condition pour afficher la communication inter-thread. La classe Lock est une fonction fournie par la classe Java. L'API riche rend la fonction de synchronisation de la classe Lock plus puissante que la synchronisation synchronisée.
Dans le package java.util. Concurrent, il y a 3 interfaces, Condition et lock (verrouillage standard). ReadWriteLock (verrouillage en lecture-écriture)
L'implémentation de Lock fournit une gamme d'opérations de verrouillage plus large que celle qui peut être obtenue à l'aide de méthodes et d'instructions synchronisées. Ils permettent une structuration plus flexible, peuvent avoir des propriétés complètement différentes et peuvent prendre en charge plusieurs conditions d'objets associés.
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
lock() signifie verrouillage, unlock() signifie déverrouillage La documentation officielle du JDK explique
Tous sont connus classes d'implémentation :
Verrou réentrant ReentrantLock Verrouillage en lecture ReentrantReadWriteLock.ReadLock
Verrouillage en écriture ReentrantReadWriteLock.writeLock
Constructeur de code source sous-jacent ReentrantLock
Fair Lock : très équitable, premier arrivé, premier servi. Mais le problème est que si un processus 3s et 3h arrivent, 3h vient en premier, puis 3s attend 3h, ce qui n'est en fait pas bon.
Verrouillage injuste : Très injuste, vous pouvez sauter la file d'attente (par défaut)
Nous l'expliquerons en détail plus tard.
Comment utiliser, verrouiller avant utilisation, déverrouiller après utilisation
//trilogie de verrouillage //1.new ReentranLock() ;Construction
//2.Lock.lock(); Lock
//3.finally(); Unlock
public class SaleTicketDemo2 { public static void main(String[] args) { //并发,多个线程操作同一个资源类,把资源类丢入线程 Ticket ticket=new Ticket(); //Runnable借口是一个FunationalInterface函数式接口,接口可以new,jdk1.8以后,lamda表达式()->{代码} new Thread(()->{for(int i=0;i{for(int i=0;i{for(int i=0;i0){ System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number); } } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } }}
La différence entre Synchronisé et verrouillé
1.synchronized est un mot-clé Java intégré et lock est une classe Java 2.synchronized ne peut pas déterminer l'état d'acquisition du verrou, mais lock peut déterminer si le verrou a été acquis
3. synchronisé sera automatiquement Pour débloquer le verrou (a–), le verrou doit être déverrouillé manuellement ! Si le verrou n'est pas libéré, il en résultera un blocage
4. Thread synchronisé 1 (obtention du verrou, blocage), thread 2 (attente, attente bête)
lock.tryLock() tente d'acquérir le verrou, ce qui cela ne se produit pas toujours Attendez
5. Les verrous synchronisés sont des verrous réentrants, ininterruptibles et injustes. Verrouillage, verrouillage réentrant, le verrouillage peut être jugé, juste et injuste peut être défini par vous-même (peut être défini par vous-même)
6. synchronisé convient à une petite quantité de problèmes de synchronisation de code, le verrouillage de verrouillage convient au verrouillage d'un grand quantité de code synchronisé
objets de verrouillage synchronisés et méthodes de bloc de code synchronisé
Problèmes traditionnels des producteurs et des consommateurs
Les producteurs et les consommateurs traditionnels sont basés sur la méthode d'attente et de notification et la clé synchronisée de la classe Object. Des mots sont utilisés pour y parvenir. Lors des entretiens, il est très courant d'écrire à la main le code du producteur et du consommateur.
Questions classiques dans les entretiens écrits :
Mode Singleton + algorithme de tri + producteur consommateur + impasse
Version synchronisée du problème producteur consommateur
Problèmes de communication entre les threads : Producteurs et consommateurs attendent réveil, réveil par notification Les threads exécutent alternativement A B pour faire fonctionner la même variable numéro=0
A num+1
B num-1
Remarque :
Dans le méthode de verrouillage, l'idée d'exécution est de juger et d'attendre + business + notification
package testConcurrent;/* 线程之间的通信问题:生产者和消费者问题 等待唤醒,通知唤醒 线程交替执行 A B 操作同一个变量number=0 A num+1 B num-1 * */public class A { public static void main(String[] args) { Data data =new Data(); new Thread(()->{ for(int i=0;i{ for(int i=0;i"+number); //通知其他线程,我+1完毕了 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException{ if(number==0){ //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我-1完毕了 this.notify(); }}
Comme le montre la figure, la fonction requise peut en principe être réalisée, mais il y aura toujours problèmes. , si j'ajoute deux fils de discussion supplémentaires à ce moment-là, alors
new Thread(()->{ for(int i=0;i{ for(int i=0;i<p><img src="https://img.php.cn/upload/article/000/000/052/4011c6e510a595c0afc39326e031cf33-4.png" alt="Java introduit les problèmes de verrouillage et de producteur-consommateur"><br> 这里结果中出现了2,输出结果出现了问题。为什么呢?<br> 为什么if判断会出现问题:<br> if判断只判断一次。因为if判断了之后,就已经进入了代码的等待那一行,这时,在wait下的线程可能有多个,甚至包括生产者和消费者。有可能某个生产者执行完了之后,唤醒的是另一个生产者。</p><p>在我们的官方文档中就给出了解释</p><pre class="brush:php;toolbar:false">public final void wait(long timeout) throws InterruptedException
导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过。
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:
synchronized (obj) { while (<condition>) obj.wait(timeout); ... // Perform action appropriate to condition }</condition>
注意点:防止虚假唤醒问题。
我们代码中用的是if判断,而应该用while判断
package testConcurrent;/* 线程之间的通信问题:生产者和消费者问题 等待唤醒,通知唤醒 线程交替执行 A B 操作同一个变量number=0 A num+1 B num-1 * */public class A { public static void main(String[] args) { Data data =new Data(); new Thread(()->{ for(int i=0;i{ for(int i=0;i{ for(int i=0;i{ for(int i=0;i"+number); //通知其他线程,我+1完毕了 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException{ while(number==0){ //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我-1完毕了 this.notify(); }}
Lock版的生产者和消费者问题
在synchronized版本中,我们使用了wait和notify来实现线程之间的同步
在lock中,
此时synchronized被lock替换了,那么wait和notify用什么来替换呢?
我们在官方文档java.util.concurrent.locks 中,找到Lock类,然后在底部找到了
Condition newCondition()
返回一个新Condition绑定到该实例Lock实例。
在等待条件之前,锁必须由当前线程保持。 呼叫Condition.await()将在等待之前将原子释放锁,并在等待返回之前重新获取锁。
然后我们再来了解Condition类
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。
我们可以看到,使用的时候new一个Condition对象。然后用await替代wait,signal替换notify
代码实现
//判断等待+业务+通知
class Data2{ //数字。资源类 private int number=0; Lock lock=new ReentrantLock(); Condition condition=lock.newCondition(); //+1 public void increment() throws InterruptedException{ try { lock.lock(); //业务代码 while(number!=0){ //等待 condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); condition.signalAll(); //通知 } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } } //-1 public void decrement() throws InterruptedException{ try { lock.lock(); //业务代码 while(number!=1){ //等待 condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); condition.signalAll(); //通知 } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } } }
注意:主函数部分于最上面的代码一样。
这时候虽然说是正确的,但是它是一个随机分布的状态,现在我们希望它有序执行,即A执行完了执行B,B执行C,C完了执行D。即精准通知。
Condition实现精准通知唤醒
Condition实现精准的通知和唤醒
我们构造三个线程,要求A执行完了执行B,B执行完了执行C,C执行完了执行D.
代码思想:
//加多个监视器,通过监视器来判断唤醒的是哪一个人
//设置多个同步监视器,每个监视器监视一个线程
//实例:生产线,下单->支付->交易->物流
package testConcurrent;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/* A执行完调用B,B执行完调用C,C执行完调用A * */public class C { public static void main(String[] args) { Data3 data=new Data3(); new Thread(()->{ for(int i=0;i{ for(int i=0;i{ for(int i=0;i支付->交易->物流 private Condition condition1=lock.newCondition(); private Condition condition2=lock.newCondition(); private Condition condition3=lock.newCondition(); private int number=1; //1A 2B 3C public void printA(){ lock.lock(); try { //业务,判断->执行->通知 while(number!=1){ //等待 condition1.await(); } System.out.println(Thread.currentThread().getName()+"=>AAAAAAA"); //唤醒,唤醒指定的人,B number=2; //精准唤醒 condition2.signal(); } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } } public void printB(){ lock.lock(); try { //业务,判断->执行->通知 while(number!=2){ //等待 condition2.await(); } System.out.println(Thread.currentThread().getName()+"=>BBBBBBB"); //唤醒,唤醒指定的人,C number=3; //精准唤醒 condition3.signal(); } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } } public void printC(){ lock.lock(); try { //业务,判断->执行->通知 while(number!=3){ //等待 condition3.await(); } System.out.println(Thread.currentThread().getName()+"=>CCCCCCC"); //唤醒,唤醒指定的人,A number=1; //精准唤醒 condition1.signal(); } catch (Exception e) { // TODO: handle exception }finally{ lock.unlock(); } }}
相关学习推荐:java基础
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!