簡體   English   中英

Java多線程在同一塊中等待並通知

[英]Java multithreading wait and notify in same block

我正在研究一個小問題,我需要以交替的方式順序打印兩個線程的數字。 像線程1打印1,線程2打印2,線程1打印3,依此類推......

所以我創建了下面的代碼,但在某些時候,兩個線程都進入等待狀態,並且沒有任何東西在控制台上打印。

import java.util.concurrent.atomic.AtomicInteger;

public class MultiPrintSequence {

    public static void main(String[] args) {
        AtomicInteger integer=new AtomicInteger(0);
        Sequence sequence1=new Sequence(integer);
        Sequence sequence2=new Sequence(integer);
        sequence1.start();
        sequence2.start();
    }
}

class Sequence extends Thread{

    private AtomicInteger integer;
    boolean flag=false;

    public Sequence(AtomicInteger integer) {
        this.integer=integer;
    }

    @Override
    public void run() {
        while(true) {
            synchronized (integer) {
                while (flag) {
                    flag=false;
                    try {
                        System.out.println(Thread.currentThread().getName()+" waiting");
                        integer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+" "+integer.incrementAndGet());
                flag = true;
                System.out.println(Thread.currentThread().getName()+" notifying");
                integer.notify();
            }
        }
    }
}

在觀察控制台輸出時,我注意到在其中一個線程通知的某個時刻,另一個線程最終在通知線程進入等待狀態之前啟動,因此在一個點上兩個線程都進入等待狀態。 下面是控制台輸出的一小部分。

Thread-1 510
Thread-1 notifying
Thread-1 waiting
Thread-0 511
Thread-0 notifying
Thread-0 waiting
Thread-1 512
Thread-1 notifying
Thread-1 waiting
**Thread-0 513
Thread-0 notifying
Thread-1 514
Thread-1 notifying
Thread-1 waiting
Thread-0 waiting**

考慮一下這個不幸的事件序列。 Thread1遞增該值,將該標志設置為true並通知等待集中的任何線程以進行鎖定。 現在Thread0已經在等待集中了。 然后Thread0醒來,它的標志= false。 然后Thread0退出while循環並打印遞增的值並通知任何等待的線程。 然后它繼續進行while循環中的下一次迭代,並對鎖定對象調用wait。 但是Thread1沒有處於等待狀態,而是在完成它的同步塊之后由調度程序從CPU切換出來以給予Thread0機會。 Thread1處於可運行狀態,由於沒有剩余的可運行線程,因此調度程序有機會再次返回。 然后Tread1進入while循環,因為flag = true,並在同一個鎖對象上調用wait。 現在兩個線程都處於等待狀態,並且沒有人將它們喚醒。 所以這是系統中實時鎖定的一個很好的例子。

發生這種情況是因為標志是一個實例字段,因此不在線程之間共享。 所以每個線程都有自己的標志副本。 如果將其標記為靜態變量,則兩個線程共享該值,因此問題得以解決。 這是標志聲明的外觀。

static boolean flag = false;

這是如何解決這個問題的? 好吧,考慮相同的事件序列。 現在,Thread1在鎖定對象上調用notify之前將標志值設置為true。 Thread0已處於等待狀態。 調度程序將Thread1從CPU切換下來,並為Thread0提供機會。 它開始運行,因為flag = true,它進入while循環將標志設置為false並調用鎖定對象的wait。 然后Thread0進入等待狀態,並且調度給了Thread1機會。 Thread1恢復執行並且flag = false,因此它退出while循環,打印遞增的值並通知等待的Thread。 所以現在沒有活鎖。

但是,我沒有看到使用synchronized和非阻塞原子變量的任何意義。 你不應該同時使用它們。 下面給出了更好的,高性能的實現。

public class Sequence extends Thread {
    private static final Object lock = new Object();
    private static int integer = 0;
    static boolean flag = false;

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                while (flag) {
                    flag = false;
                    try {
                        System.out.println(Thread.currentThread().getName() + " waiting");
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + " " + ++integer);
                flag = true;
                System.out.println(Thread.currentThread().getName() + " notifying");
                lock.notify();
            }
        }
    }

    public static void main(String[] args) {
        Sequence sequence1=new Sequence();
        Sequence sequence2=new Sequence();
        sequence1.start();
        sequence2.start();
    }
}

您的flag變量不在線程之間共享,但該標志周圍的邏輯仍然是奇數。 請注意,在使用synchronized時不需要使用AtomicInteger

正確使用synchronized時,普通的int變量已足以實現整個邏輯:

public class MultiPrintSequence {
    public static void main(String[] args) {
        final Sequence sequence = new Sequence();
        new Thread(sequence).start();
        new Thread(sequence).start();
    }
}
class Sequence implements Runnable {
    private final Object lock = new Object();
    private int sharedNumber;

    @Override
    public void run() {
        synchronized(lock) {
            for(;;) {
                int myNum = ++sharedNumber;
                lock.notify();
                System.out.println(Thread.currentThread()+": "+myNum);
                while(sharedNumber == myNum) try {
                    lock.wait();
                } catch (InterruptedException ex) {
                    throw new AssertionError(ex);
                }
            }
        }
    }
}

當然,創建多個線程來執行順序操作會破壞並發編程的實際目的。

在代碼中,即使整數是Atomic並在Threads之間共享,標志本身也不是。

class Sequence extends Thread{

    private AtomicInteger integer; //shared
    boolean flag=false; //local

    public Sequence(AtomicInteger integer) {
      this.integer=integer;
    }

這導致一個線程的變化沒有反映在另一個線程中。

建議的解決方案:

您也可以使用Atomic解決標志並進行共享,例如:

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public class StackOverflow {

    public static void main(String[] args) {
        AtomicInteger integer=new AtomicInteger(0);
        AtomicBoolean flag=new AtomicBoolean(true);
        Sequence sequence1=new Sequence(integer, flag);
        Sequence sequence2=new Sequence(integer, flag);
        sequence1.start();
        sequence2.start();
    }
}

順序:

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

class Sequence extends Thread{

    private final AtomicInteger integer;
    private AtomicBoolean flag;

    public Sequence(AtomicInteger integer, AtomicBoolean flag) {
        this.integer=integer;
        this.flag=flag;
    }

    @Override
    public void run() {
        while(true) {
            synchronized (integer) {
                while (flag.get()) {
                    flag.set(false);
                    try {
                        System.out.println(Thread.currentThread().getName()+" waiting");
                        integer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+" "+integer.incrementAndGet());
                flag.set(true);
                System.out.println(Thread.currentThread().getName()+" notifying");
                integer.notify();
            }
        }
    }
}

這是輸出的一部分:

Thread-1 8566
Thread-1 notifying
Thread-1 waiting
Thread-0 8567
Thread-0 notifying
Thread-0 waiting
Thread-1 8568
Thread-1 notifying
Thread-1 waiting
Thread-0 8569
Thread-0 notifying
Thread-0 waiting

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM