Heim  >  Artikel  >  Java  >  Java führt zu Lock- und Producer-Consumer-Problemen

Java führt zu Lock- und Producer-Consumer-Problemen

coldplay.xixi
coldplay.xixinach vorne
2021-02-09 17:43:212686Durchsuche

Java führt zu Lock- und Producer-Consumer-Problemen

Kostenlose Lernempfehlung: Java Basic Tutorial

Lock Lock und Producer Consumer Problem

  • Traditionelle synchronisierte Sperre
  • Sperre sperren
  • Synchronisiert und Unterschied zwischen Schlössern
  • Traditionell Produzenten- und Verbraucherprobleme
  • Sperrversion von Produzenten- und Verbraucherproblemen
  • Bedingung zum Erreichen einer präzisen Benachrichtigungsaktivierung

Traditionelle synchronisierte Sperre

Implementieren Sie ein einfaches Ticketing-Beispiel:

/*
真正的多线程开发,公司中的开发,降低耦合性
线程就是一个单独的资源类,没有任何附属的操作
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);
        }
    }}

Beachten Sie, dass diese Lambda-Ausdrücke in verwendet werden Eine ausführliche Beschreibung von Lambda-Ausdrücken finden Sie unter Java-Grundlagen – Lambda-Ausdrücke. Dabei werden traditionelle Synchronisierungen verwendet, um Parallelität zu erreichen. Das Wesentliche bei Synchronisierungen sind Warteschlangen und Sperren. Es ist, als würde man in einer Cafeteria Schlange stehen. Gäbe es keine Warteschlangen, gäbe es Chaos. Erst wenn die Dienstleistung für eine Person abgeschlossen ist, kann die andere Person die Dienstleistung erhalten.

Java führt zu Lock- und Producer-Consumer-ProblemenLock
Wie bereits erwähnt, stellt die JVM das synchronisierte Schlüsselwort bereit, um einen synchronen Zugriff auf Variablen zu erreichen, und verwendet Wait und Notify, um eine Kommunikation zwischen Threads zu erreichen. Nach jdk1.5 stellt JAVA die Lock-Klasse bereit, um dieselben Funktionen wie synchronisiert zu implementieren, und stellt außerdem eine Bedingung bereit, um die Kommunikation zwischen Threads anzuzeigen.

Die Lock-Klasse ist eine von der Java-Klasse bereitgestellte Funktion. Die umfangreiche API macht die Synchronisierungsfunktion der Lock-Klasse leistungsfähiger als die synchronisierte Synchronisierung.

Im java.util Concurrent-Paket gibt es 3 Schnittstellen: Bedingung und Sperre (Standardsperre). Die ReadWriteLock

Lock-Implementierung bietet eine größere Auswahl an Sperrvorgängen, als mit synchronisierten Methoden und Anweisungen erreicht werden können. Sie ermöglichen eine flexiblere Strukturierung, können völlig unterschiedliche Eigenschaften haben und können mehrere zugehörige Objektbedingungen unterstützen.

Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
lock() bedeutet Sperren, unlock() bedeutet Entsperren
Erklärt in der offiziellen JDK-Dokumentation

Alle bekannten Implementierungsklassen:

ReentrantLock ReentrantLock Java führt zu Lock- und Producer-Consumer-Problemen ReentrantReadWriteLock.ReadLock Lesesperre
Sperre .writeLock Schreibsperre
Lassen Sie uns zunächst über die ReentrantLock-Implementierungsklasse sprechen: ReentrantLock zugrunde liegender Quellcodekonstruktor

Faire Sperre: Sehr fair, wer zuerst kommt, mahlt zuerst. Das Problem ist jedoch, dass, wenn ein 3s- und ein 3h-Prozess eintrifft, 3h zuerst kommt und dann 3s auf 3h wartet, was eigentlich nicht gut ist.

Unfaire Sperre: Sehr unfair, Sie können sich anstellen (Standard)

Wir werden es später im Detail erklären.

Java führt zu Lock- und Producer-Consumer-ProblemenWie zu verwenden, vor Gebrauch sperren, nach Gebrauch entsperren


//Trilogie sperren

//1.neue ReentranLock(); Struktur //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();
		}	
	}}

Der Unterschied zwischen Synchronized und Lock

1.synchronized ist ein integriertes Java-Schlüsselwort und lock ist eine Java-Klasse 2.synchronized kann den Status der Sperre nicht ermitteln, sperren Sie kann beurteilen, ob die Sperre erhalten wurde

3. Durch die Synchronisierung wird die Sperre automatisch aufgehoben (a–), die Sperre muss manuell aufgehoben werden! Wenn die Sperre nicht aufgehoben wird, führt dies zu einem Deadlock 4. Synchronisierter Thread 1 (Erhalten der Sperre, Blockieren), Thread 2 (Warten, dummes Warten) lock.tryLock() versucht, die Sperre zu erhalten, und wartet möglicherweise nicht für immer

5. Synchronisierte Wiedereintrittssperre, unterbrechungsfreie, unfaire Sperre. Sperre, wiedereintretende Sperre, kann die Sperre beurteilen, fair und unfair, kann von Ihnen selbst eingestellt werden (kann von Ihnen selbst eingestellt werden) Synchronisierter Code

Synchronisierte Sperrobjekte und synchronisierte Codeblöcke Methode


Traditionelle Produzenten- und Konsumentenprobleme


Traditionelle Produzenten und Konsumenten werden basierend auf der Warte-, Benachrichtigungsmethode und dem synchronisierten Schlüsselwort der Objektklasse implementiert.
Bei Vorstellungsgesprächen kommt es häufig vor, dass Produzenten- und Konsumentencode von Hand geschrieben werden.

Klassische Fragen im schriftlichen Interviewtest:

Singleton-Modus + Sortieralgorithmus + Producer Consumer + Deadlock synchronisierte Version des Producer-Consumer-Problems

Kommunikationsproblem zwischen Threads: Producer- und Consumer-Problem warten darauf, aufzuwachen, Benachrichtigung zum Aufwachen
Threads Führen Sie abwechselnd A B aus und bedienen Sie dieselbe Variable number=0
A num+1
B num-1

Hinweis:

Bei der Sperrmethode besteht die Ausführungsidee darin, Warten + Geschäft + Benachrichtigung zu beurteilen

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();
	}}


Wie Abbildung: Im Grunde können die erforderlichen Funktionen erreicht werden, aber es wird immer noch Probleme geben, wenn ich zu diesem Zeitpunkt zwei weitere Threads hinzufüge, dann

		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 führt zu Lock- und Producer-Consumer-Problemen"><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()方法。

Java führt zu Lock- und Producer-Consumer-Problemen
我们可以看到,使用的时候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();
		}
	}	}

注意:主函数部分于最上面的代码一样。

Java führt zu Lock- und Producer-Consumer-Problemen
这时候虽然说是正确的,但是它是一个随机分布的状态,现在我们希望它有序执行,即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基础

Das obige ist der detaillierte Inhalt vonJava führt zu Lock- und Producer-Consumer-Problemen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:csdn.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen