Heim  >  Artikel  >  Java  >  Wie löst man Probleme bei der Verwendung von Polling-Sperren in Java?

Wie löst man Probleme bei der Verwendung von Polling-Sperren in Java?

WBOY
WBOYnach vorne
2023-05-09 17:10:081266Durchsuche

Problemdemonstration

Wenn wir keine Abfragesperren verwendet haben, kann dieses Problem auftreten:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadLockByReentrantLock {
    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // 创建锁 A
        Lock lockB = new ReentrantLock(); // 创建锁 B

        // 创建线程 1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                lockA.lock(); // 加锁
                System.out.println("线程 1:获取到锁 A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 1:等待获取 B...");
                    lockB.lock(); // 加锁
                    try {
                        System.out.println("线程 1:获取到锁 B!");
                    } finally {
                        lockA.unlock(); // 释放锁
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); // 释放锁
                }
            }
        });
        t1.start(); // 运行线程

        // 创建线程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lockB.lock(); // 加锁
                System.out.println("线程 2:获取到锁 B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 2:等待获取 A...");
                    lockA.lock(); // 加锁
                    try {
                        System.out.println("线程 2:获取到锁 A!");
                    } finally {
                        lockA.unlock(); // 释放锁
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockB.unlock(); // 释放锁
                }
            }
        });
        t2.start(); // 运行线程
    }
}

#🎜🎜 #Die Ausführung Die Ergebnisse des obigen Codes lauten wie folgt:

Wie löst man Probleme bei der Verwendung von Polling-Sperren in Java?

Wie aus den obigen Ergebnissen ersichtlich ist, warten Threads in der auf einander Programm zu diesem Zeitpunkt, und der Versuch, die (Sperr-)Ressource der anderen Partei zu erhalten, ist ein typisches Deadlock-Problem.

Einfache Version der Polling-Sperre

Wenn ein Deadlock-Problem auftritt, können wir die Polling-Sperre verwenden, um es durch Polling zu lösen Schlägt die Sperrenerfassung während des Prozesses fehl, wird ein Rollback-Vorgang durchgeführt, um alle Sperren des aktuellen Threads aufzuheben und auf die nächste erneute Ausführung zu warten. Dadurch kann vermieden werden, dass mehrere Threads gleichzeitig Sperrressourcen besitzen und belegen. Dadurch wird das Deadlock-Problem direkt gelöst.

Die einfache Version der Abfragesperre wird wie folgt implementiert:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SolveDeadLockExample2 {
    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // 创建锁 A
        Lock lockB = new ReentrantLock(); // 创建锁 B

        // 创建线程 1(使用轮询锁)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用轮询锁
                pollingLock(lockA, lockB);
            }
        });
        t1.start(); // 运行线程

        // 创建线程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lockB.lock(); // 加锁
                System.out.println("线程 2:获取到锁 B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 2:等待获取 A...");
                    lockA.lock(); // 加锁
                    try {
                        System.out.println("线程 2:获取到锁 A!");
                    } finally {
                        lockA.unlock(); // 释放锁
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockB.unlock(); // 释放锁
                }
            }
        });
        t2.start(); // 运行线程
    }

    /**
     * 轮询锁
     */
    private static void pollingLock(Lock lockA, Lock lockB) {
        // 轮询锁
        while (true) {
            if (lockA.tryLock()) { // 尝试获取锁
                System.out.println("线程 1:获取到锁 A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 1:等待获取 B...");
                    if (lockB.tryLock()) { // 尝试获取锁
                        try {
                            System.out.println("线程 1:获取到锁 B!");
                        } finally {
                            lockB.unlock(); // 释放锁
                            System.out.println("线程 1:释放锁 B.");
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); // 释放锁
                    System.out.println("线程 1:释放锁 A.");
                }
            }
            // 等待一秒再继续执行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Die Ausführungsergebnisse des obigen Codes sind wie folgt: #🎜🎜 #

Wie löst man Probleme bei der Verwendung von Polling-Sperren in Java?Aus den obigen Ergebnissen ist ersichtlich, dass es bei Verwendung der Abfragesperre im Programm kein Deadlock-Problem gibt, in der obigen Runde jedoch Abfragesperren nicht perfekt. Werfen wir einen Blick darauf, welche Probleme diese Wahlsperre haben wird.

Problem 1: Endlosschleife

Die obige einfache Version der Abfragesperre: Wenn es einen Thread gibt, der die Sperrressource belegt oder sie schon lange belegt hat, wird er ausgeführt Dies führt dazu, dass die Abfragesperre in einen Endlosschleifenzustand übergeht. Sie versucht ständig, Sperrressourcen zu erhalten, was zu neuen Problemen und unnötigem Leistungsaufwand führt.

Gegenbeispiel

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SolveDeadLockExample {
    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // 创建锁 A
        Lock lockB = new ReentrantLock(); // 创建锁 B

        // 创建线程 1(使用轮询锁)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用轮询锁
                pollingLock(lockA, lockB);
            }
        });
        t1.start(); // 运行线程

        // 创建线程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lockB.lock(); // 加锁
                System.out.println("线程 2:获取到锁 B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 2:等待获取 A...");
                    lockA.lock(); // 加锁
                    try {
                        System.out.println("线程 2:获取到锁 A!");
                    } finally {
                        lockA.unlock(); // 释放锁
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 如果此处代码未执行,线程 2 一直未释放锁资源
                    // lockB.unlock(); 
                }
            }
        });
        t2.start(); // 运行线程
    }

    /**
     * 轮询锁
     */
    public static void pollingLock(Lock lockA, Lock lockB) {
        while (true) {
            if (lockA.tryLock()) { // 尝试获取锁
                System.out.println("线程 1:获取到锁 A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("线程 1:等待获取 B...");
                    if (lockB.tryLock()) { // 尝试获取锁
                        try {
                            System.out.println("线程 1:获取到锁 B!");
                        } finally {
                            lockB.unlock(); // 释放锁
                            System.out.println("线程 1:释放锁 B.");
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); // 释放锁
                    System.out.println("线程 1:释放锁 A.");
                }
            }
            // 等待一秒再继续执行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Das Ausführungsergebnis des obigen Codes ist wie folgt:

# 🎜🎜## 🎜🎜#Aus den obigen Ergebnissen ist ersichtlich, dass die Abfragesperre von Thread 1 in einen Endlosschleifenzustand eingetreten ist.

Wie löst man Probleme bei der Verwendung von Polling-Sperren in Java?Optimierte Version

Angesichts der oben genannten Endlosschleifensituation gibt es zwei Möglichkeiten, wie wir sie verbessern können:

#🎜 🎜 #

Fügen Sie die maximale Anzahl von Malen hinzu:

Wenn die Sperre nach n Versuchen, die Sperre zu erwerben, nicht erworben wurde, wird davon ausgegangen, dass die Sperrenerfassung fehlgeschlagen ist , und die Runde wird nach Ausführung der Fehlerstrategieabfrage beendet (Fehlerstrategie kann Protokollierung oder andere Vorgänge sein); 🎜🎜#Wenn n Sekunden vergangen sind, versuchen Sie, die Sperre zu erhalten. Wenn die Sperre danach nicht erhalten wurde, wird davon ausgegangen, dass die Sperre fehlgeschlagen ist, und die Abfrage wird beendet, nachdem die Fehlerrichtlinie ausgeführt wurde.
  • Jede der oben genannten Strategien kann das Problem der Endlosschleife lösen. Aus Gründen der Implementierungskosten können wir die maximale Anzahl von Umfragen verwenden, um die Abfrage zu verbessern ,

  • Der spezifische Implementierungscode lautet wie folgt:
  • import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class SolveDeadLockExample {
    
        public static void main(String[] args) {
            Lock lockA = new ReentrantLock(); // 创建锁 A
            Lock lockB = new ReentrantLock(); // 创建锁 B
    
            // 创建线程 1(使用轮询锁)
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    // 调用轮询锁
                    pollingLock(lockA, lockB, 3);
                }
            });
            t1.start(); // 运行线程
    
            // 创建线程 2
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    lockB.lock(); // 加锁
                    System.out.println("线程 2:获取到锁 B!");
                    try {
                        Thread.sleep(1000);
                        System.out.println("线程 2:等待获取 A...");
                        lockA.lock(); // 加锁
                        try {
                            System.out.println("线程 2:获取到锁 A!");
                        } finally {
                            lockA.unlock(); // 释放锁
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // 线程 2 忘记释放锁资源
                        // lockB.unlock(); // 释放锁
                    }
                }
            });
            t2.start(); // 运行线程
        }
    
        /**
         * 轮询锁
         *
         * maxCount:最大轮询次数
         */
        public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
            // 轮询次数计数器
            int count = 0;
            while (true) {
                if (lockA.tryLock()) { // 尝试获取锁
                    System.out.println("线程 1:获取到锁 A!");
                    try {
                        Thread.sleep(1000);
                        System.out.println("线程 1:等待获取 B...");
                        if (lockB.tryLock()) { // 尝试获取锁
                            try {
                                System.out.println("线程 1:获取到锁 B!");
                            } finally {
                                lockB.unlock(); // 释放锁
                                System.out.println("线程 1:释放锁 B.");
                                break;
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lockA.unlock(); // 释放锁
                        System.out.println("线程 1:释放锁 A.");
                    }
                }
    
                // 判断是否已经超过最大次数限制
                if (count++ > maxCount) {
                    // 终止循环
                    System.out.println("轮询锁获取失败,记录日志或执行其他失败策略");
                    return;
                }
    
                // 等待一秒再继续尝试获取锁
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    Die Ausführungsergebnisse des obigen Codes lauten wie folgt: # 🎜🎜##🎜🎜 #

Aus den obigen Ergebnissen ist ersichtlich, dass die Abfragesperre nach der Verbesserung nicht mehr das Problem einer Endlosschleife aufweist bricht die Ausführung nach einer bestimmten Anzahl von Versuchen ab.

Problem 2: Thread-Aushungern

Die Abfragewartezeit unserer oben genannten Abfragesperre ist eine feste Zeit, wie im folgenden Code gezeigt:

// 等待 1s 再尝试获取(轮询)锁
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
Dies führt unter besonderen Umständen zum Problem des Thread-Aushungerns, dh die Abfragesperre kann die Sperre nicht erhalten, wie im folgenden Beispiel.

GegenbeispielWie löst man Probleme bei der Verwendung von Polling-Sperren in Java?

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SolveDeadLockExample {

    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // 创建锁 A
        Lock lockB = new ReentrantLock(); // 创建锁 B

        // 创建线程 1(使用轮询锁)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用轮询锁
                pollingLock(lockA, lockB, 3);
            }
        });
        t1.start(); // 运行线程

        // 创建线程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    lockB.lock(); // 加锁
                    System.out.println("线程 2:获取到锁 B!");
                    try {
                        System.out.println("线程 2:等待获取 A...");
                        lockA.lock(); // 加锁
                        try {
                            System.out.println("线程 2:获取到锁 A!");
                        } finally {
                            lockA.unlock(); // 释放锁
                        }
                    } finally {
                        lockB.unlock(); // 释放锁
                    }
                    // 等待一秒之后继续执行
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t2.start(); // 运行线程
    }

    /**
     * 轮询锁
     */
    public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
        // 循环次数计数器
        int count = 0;
        while (true) {
            if (lockA.tryLock()) { // 尝试获取锁
                System.out.println("线程 1:获取到锁 A!");
                try {
                    Thread.sleep(100); // 等待 0.1s(获取锁需要的时间)
                    System.out.println("线程 1:等待获取 B...");
                    if (lockB.tryLock()) { // 尝试获取锁
                        try {
                            System.out.println("线程 1:获取到锁 B!");
                        } finally {
                            lockB.unlock(); // 释放锁
                            System.out.println("线程 1:释放锁 B.");
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); // 释放锁
                    System.out.println("线程 1:释放锁 A.");
                }
            }

            // 判断是否已经超过最大次数限制
            if (count++ > maxCount) {
                // 终止循环
                System.out.println("轮询锁获取失败,记录日志或执行其他失败策略");
                return;
            }

            // 等待一秒再继续尝试获取锁
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Das Ausführungsergebnis des obigen Codes ist wie folgt:

# 🎜🎜# Aus den obigen Ergebnissen ist ersichtlich, dass Thread 1 (Abfragesperre) die Sperre nie erfolgreich erhalten hat. Der Grund für dieses Ergebnis ist, dass die Wartezeit von Thread 1 für jede Abfrage fest 1s und Thread beträgt 2 hat auch eine feste Wartezeit von 1 Sekunde. Bei der gleichen Häufigkeit wird die Sperre alle 1 Sekunde erworben, was dazu führt, dass Thread 2 die Sperre immer zuerst erfolgreich erhält, während sich Thread 1 immer in einer „Hunger“-Situation befindet 🎜#Der Ausführungsprozess ist wie in der folgenden Abbildung dargestellt: # 🎜🎜#

optimierte Version

Als nächstes werden wir kann die feste Wartezeit der Abrufsperre einstellen, #🎜🎜 #Verbessert auf eine Methode mit fester Zeit + zufälliger Zeit, um das Problem des „Aushungerns“ von Abrufsperren aufgrund der gleichen Häufigkeit des Erwerbs von Sperren zu vermeiden . Der spezifische Implementierungscode lautet wie folgt: #🎜 🎜#

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SolveDeadLockExample {
    private static Random rdm = new Random();

    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // 创建锁 A
        Lock lockB = new ReentrantLock(); // 创建锁 B

        // 创建线程 1(使用轮询锁)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用轮询锁
                pollingLock(lockA, lockB, 3);
            }
        });
        t1.start(); // 运行线程

        // 创建线程 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    lockB.lock(); // 加锁
                    System.out.println("线程 2:获取到锁 B!");
                    try {
                        System.out.println("线程 2:等待获取 A...");
                        lockA.lock(); // 加锁
                        try {
                            System.out.println("线程 2:获取到锁 A!");
                        } finally {
                            lockA.unlock(); // 释放锁
                        }
                    } finally {
                        lockB.unlock(); // 释放锁
                    }
                    // 等待一秒之后继续执行
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t2.start(); // 运行线程
    }

    /**
     * 轮询锁
     */
    public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
        // 循环次数计数器
        int count = 0;
        while (true) {
            if (lockA.tryLock()) { // 尝试获取锁
                System.out.println("线程 1:获取到锁 A!");
                try {
                    Thread.sleep(100); // 等待 0.1s(获取锁需要的时间)
                    System.out.println("线程 1:等待获取 B...");
                    if (lockB.tryLock()) { // 尝试获取锁
                        try {
                            System.out.println("线程 1:获取到锁 B!");
                        } finally {
                            lockB.unlock(); // 释放锁
                            System.out.println("线程 1:释放锁 B.");
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); // 释放锁
                    System.out.println("线程 1:释放锁 A.");
                }
            }

            // 判断是否已经超过最大次数限制
            if (count++ > maxCount) {
                // 终止循环
                System.out.println("轮询锁获取失败,记录日志或执行其他失败策略");
                return;
            }

            // 等待一定时间(固定时间 + 随机时间)之后再继续尝试获取锁
            try {
                Thread.sleep(300 + rdm.nextInt(8) * 100); // 固定时间 + 随机时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Wie löst man Probleme bei der Verwendung von Polling-Sperren in Java?Die Ausführungsergebnisse des obigen Codes lauten wie folgt:

# 🎜🎜#

Aus den obigen Ergebnissen können Sie erkennen, dass das Problem des Thread-Aushungerns nicht auftritt, nachdem Thread 1 (Abfragesperre) eine zufällige Wartezeit hinzugefügt hat.

Das obige ist der detaillierte Inhalt vonWie löst man Probleme bei der Verwendung von Polling-Sperren in Java?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

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