亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

沒有print語句,循環看不到其他線程更改的值

沒有print語句,循環看不到其他線程更改的值

沒有print語句,循環看不到其他線程更改的值在我的代碼中,我有一個循環,等待從不同的線程更改某個狀態。另一個線程工作,但我的循環永遠不會看到更改的值。它永遠等待。但是,當我System.out.println在循環中放置一個語句時,它突然起作用了!為什么?以下是我的代碼示例:class MyHouse {     boolean pizzaArrived = false;     void eatPizza() {         while (pizzaArrived == false) {             //System.out.println("waiting");         }         System.out.println("That was delicious!");     }     void deliverPizza() {         pizzaArrived = true;     }}while循環正在運行時,我deliverPizza()從另一個線程調用來設置pizzaArrived變量。但循環僅在我取消注釋System.out.println("waiting");語句時才有效。這是怎么回事?
查看完整描述

1 回答

?
猛跑小豬

TA貢獻1858條經驗 獲得超8個贊

允許JVM假定其他線程pizzaArrived在循環期間不更改變量。換句話說,它可以pizzaArrived == false在循環外提升測試,優化:

while (pizzaArrived == false) {}

進入這個:

if (pizzaArrived == false) while (true) {}

這是一個無限循環。

要確保一個線程所做的更改對其他線程可見,您必須始終在線程之間添加一些同步。最簡單的方法是創建共享變量volatile

volatile boolean pizzaArrived = false;

創建變量可以volatile保證不同的線程可以看到彼此對其的更改的影響。這可以防止JVM緩存pizzaArrived循環外的測試值或將測試掛起。相反,它必須每次都讀取實變量的值。

(更正式地說,在訪問變量之間volatile創建了一個先發生的關系。這意味著在傳遞披薩之前線程所做的所有其他工作對于接收披薩的線程也是可見的,即使那些其他更改不是volatile變量。)

同步方法主要用于實現互斥(防止兩件事同時發生),但它們也具有所有相同的副作用volatile。在讀取和寫入變量時使用它們是另一種使更改對其他線程可見的方法:

class MyHouse {
    boolean pizzaArrived = false;

    void eatPizza() {
        while (getPizzaArrived() == false) {}
        System.out.println("That was delicious!");
    }

    synchronized boolean getPizzaArrived() {
        return pizzaArrived;
    }

    synchronized void deliverPizza() {
        pizzaArrived = true;
    }}

打印聲明的效果

System.out是一個PrintStream對象。方法是PrintStream這樣同步的:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }}

同步防止pizzaArrived在循環期間緩存。嚴格地說,兩個線程必須在同一個對象上同步,以保證對變量的更改是可見的。(例如,println在設置之后pizzaArrived調用并在讀取之前再次調用它將pizzaArrived是正確的。)如果只有一個線程在特定對象上同步,則允許JVM忽略它。在實踐中,JVM不夠智能,無法證明其他線程println在設置后不會調用pizzaArrived,因此它假定它們可能。因此,如果調用它,它不能在循環期間緩存變量System.out.println。這就是為什么這樣的循環在有打印語句時起作用的原因,盡管它不是正確的解決方法。

使用System.out不是導致這種效果的唯一方法,但它是人們最常發現的,當他們試圖調試為什么他們的循環不起作用時!


更大的問題

while (pizzaArrived == false) {}是一個忙碌的等待循環。那很糟!當它等待時,它會占用CPU,從而減慢其他應用程序的速度,并增加系統的功耗,溫度和風扇速度。理想情況下,我們希望循環線程在等待時休眠,因此它不會占用CPU。

以下是一些方法:

使用wait / notify

一個低級解決方案是使用以下的wait / notify方法Object

class MyHouse {
    boolean pizzaArrived = false;

    void eatPizza() {
        synchronized (this) {
            while (!pizzaArrived) {
                try {
                    this.wait();
                } catch (InterruptedException e) {}
            }
        }

        System.out.println("That was delicious!");
    }

    void deliverPizza() {
        synchronized (this) {
            pizzaArrived = true;
            this.notifyAll();
        }
    }}

在這個版本的代碼中,循環線程調用wait(),這使線程處于休眠狀態。睡覺時不會使用任何CPU周期。在第二個線程設置變量之后,它調用notifyAll()喚醒正在等待該對象的任何/所有線程。這就像披薩家伙敲響了門鈴一樣,所以你可以坐下來休息,等待,而不是笨拙地站在門口。

在對象上調用wait / notify時,必須保持該對象的同步鎖,這就是上面的代碼所做的。你可以使用你喜歡的任何對象,只要兩個線程使用相同的對象:這里我使用this(實例MyHouse)。通常,兩個線程無法同時進入同一對象的同步塊(這是同步目的的一部分),但它在此處起作用,因為線程在wait()方法內部時臨時釋放同步鎖。

BlockingQueue的

BlockingQueue用于實現生產者 - 消費者隊列?!跋M者”從隊列前面取物品,“生產者”在后面推動物品。一個例子:

class MyHouse {
    final BlockingQueue<Object> queue = new LinkedBlockingQueue<>();

    void eatFood() throws InterruptedException {
        // take next item from the queue (sleeps while waiting)
        Object food = queue.take();
        // and do something with it
        System.out.println("Eating: " + food);
    }

    void deliverPizza() throws InterruptedException {
        // in producer threads, we push items on to the queue.
        // if there is space in the queue we can return immediately;
        // the consumer thread(s) will get to it later
        queue.put("A delicious pizza");
    }}

注意:可以拋出s 的puttake方法,它們是必須處理的已檢查異常。在上面的代碼中,為簡單起見,重新拋出了異常。您可能更喜歡捕獲方法中的異常并重試put或take調用以確保它成功。除了那一點丑陋之外,很容易使用。BlockingQueueInterruptedExceptionBlockingQueue

這里不需要其他同步,因為BlockingQueue確保在將項目放入隊列之前所做的所有線程對于將這些項目取出的線程是可見的。

執行人

Executors就像現成的BlockingQueue執行任務一樣。例:

// A "SingleThreadExecutor" has one work thread and an unlimited queueExecutorService executor = Executors.newSingleThreadExecutor();
Runnable eatPizza = () -> { System.out.println("Eating a delicious pizza"); };Runnable cleanUp = () -> 
{ System.out.println("Cleaning up the house"); };
// we submit tasks which will be executed on the work threadexecutor.execute(eatPizza);executor.execute(cleanUp);
// we continue immediately without needing to wait for the tasks to finish

有關詳情請參閱該文檔Executor,ExecutorServiceExecutors。

事件處理

在等待用戶在UI中單擊某些內容時循環是錯誤的。而是使用UI工具包的事件處理功能。例如,在Swing中

JLabel label = new JLabel();JButton button = new JButton("Click me");button.addActionListener((ActionEvent e) -> {
    // This event listener is run when the button is clicked.
    // We don't need to loop while waiting.
    label.setText("Button was clicked");});

因為事件處理程序在事件派發線程上運行,所以在事件處理程序中執行長時間的工作會阻止與UI的其他交互,直到工作完成。可以在新線程上啟動慢速操作,或使用上述技術之一(wait / notify,a BlockingQueueExecutor)將調度分派給等待的線程。您還可以使用SwingWorker專為此設計的a,并自動提供后臺工作線程:

JLabel label = new JLabel();JButton button = new JButton("Calculate answer");// Add a click listener for the buttonbutton.
addActionListener((ActionEvent e) -> {

    // Defines MyWorker as a SwingWorker whose result type is String:
    class MyWorker extends SwingWorker<String,Void> {
        @Override
        public String doInBackground() throws Exception {
            // This method is called on a background thread.
            // You can do long work here without blocking the UI.
            // This is just an example:
            Thread.sleep(5000);
            return "Answer is 42";
        }

        @Override
        protected void done() {
            // This method is called on the Swing thread once the work is done
            String result;
            try {
                result = get();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            label.setText(result); // will display "Answer is 42"
        }
    }

    // Start the worker
    new MyWorker().execute();});

計時器

要執行定期操作,您可以使用a java.util.Timer。它比編寫自己的定時循環更容易使用,更容易啟動和停止。該演示每秒打印一次當前時間:

Timer timer = new Timer();TimerTask task = new TimerTask() {
    @Override
    public void run() {
        System.out.println(System.currentTimeMillis());
    }};timer.scheduleAtFixedRate(task, 0, 1000);

每個java.util.Timer都有自己的后臺線程,用于執行其調度的TimerTasks。當然,線程在任務之間休眠,因此它不會占用CPU。

在Swing代碼中,還有一個javax.swing.Timer類似的,但它在Swing線程上執行偵聽器,因此您可以安全地與Swing組件交互,而無需手動切換線程:

JFrame frame = new JFrame();frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);Timer timer = new Timer(1000, (ActionEvent e) -> {
    frame.setTitle(String.valueOf(System.currentTimeMillis()));});timer.setRepeats(true);timer.start();frame.setVisible(true);

其他方法

如果您正在編寫多線程代碼,那么值得探索這些包中的類以查看可用的內容:

另請參閱Java教程的Concurrency部分。多線程很復雜,但有很多幫助可用!


查看完整回答
反對 回復 2019-05-27
  • 1 回答
  • 0 關注
  • 708 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號