Làm thế nào để tạo một luồng Java chờ đầu ra của luồng khác?


128

Tôi đang tạo một ứng dụng Java với một luồng logic ứng dụng và luồng truy cập cơ sở dữ liệu. Cả hai đều tồn tại suốt đời của ứng dụng và cả hai cần phải chạy cùng một lúc (một cuộc nói chuyện với máy chủ, một cuộc nói chuyện với người dùng; khi ứng dụng được khởi động hoàn toàn, tôi cần cả hai hoạt động).

Tuy nhiên, khi khởi động, tôi cần đảm bảo rằng ban đầu luồng ứng dụng sẽ đợi cho đến khi luồng db sẵn sàng (hiện được xác định bằng cách bỏ phiếu một phương thức tùy chỉnh dbthread.isReady()). Tôi sẽ không phiền nếu luồng ứng dụng chặn cho đến khi luồng db đã sẵn sàng.

Thread.join() không giống như một giải pháp - luồng db chỉ thoát khi tắt ứng dụng.

while (!dbthread.isReady()) {} loại công việc, nhưng vòng lặp trống tiêu thụ rất nhiều chu kỳ bộ xử lý.

Còn ý tưởng nào khác không? Cảm ơn.

Câu trả lời:


128

Tôi thực sự khuyên bạn nên thực hiện một hướng dẫn như Đồng thời Java của Sun trước khi bạn bắt đầu trong thế giới đa luồng của ma thuật.

Ngoài ra còn có một số cuốn sách hay (google cho "Lập trình đồng thời trong Java", "Đồng thời Java trong thực tiễn".

Để có được câu trả lời của bạn:

Trong mã của bạn phải chờ dbThread, bạn phải có một cái gì đó như thế này:

//do some work
synchronized(objectYouNeedToLockOn){
    while (!dbThread.isReady()){
        objectYouNeedToLockOn.wait();
    }
}
//continue with work after dbThread is ready

Trong dbThreadphương pháp của bạn, bạn sẽ cần phải làm một cái gì đó như thế này:

//do db work
synchronized(objectYouNeedToLockOn){
    //set ready flag to true (so isReady returns true)
    ready = true;
    objectYouNeedToLockOn.notifyAll();
}
//end thread run method here

Các objectYouNeedToLockOnTôi đang sử dụng trong các ví dụ tốt nhất là các đối tượng mà bạn cần để thao tác đồng thời từ mỗi chủ đề, hoặc bạn có thể tạo ra một riêng biệt Objectcho mục đích đó (tôi sẽ không khuyên bạn nên làm cho các phương pháp tự đồng bộ):

private final Object lock = new Object();
//now use lock in your synchronized blocks

Để hiểu rõ hơn:
Có nhiều cách khác (đôi khi tốt hơn) để làm như trên, ví dụ như CountdownLatches, v.v. Vì Java 5 có rất nhiều lớp đồng thời tiện lợi trong java.util.concurrentgói và gói phụ. Bạn thực sự cần tìm tài liệu trực tuyến để tìm hiểu đồng thời, hoặc có được một cuốn sách hay.


Cũng không phải tất cả các mã luồng có thể được tích hợp độc đáo trong các đối tượng, nếu tôi không sai. Vì vậy, tôi không nghĩ rằng sử dụng đồng bộ hóa đối tượng là một cách tốt để bắt chước công việc liên quan đến chủ đề này.
dùng1914692

@ user1914692: Không chắc chắn những cạm bẫy nào trong việc sử dụng phương pháp trên - quan tâm để giải thích thêm?
Piskvor rời khỏi tòa nhà

1
@Piskvor: Xin lỗi tôi đã viết điều này từ lâu và tôi gần như quên mất những gì trong tâm trí của tôi. Có lẽ tôi chỉ có nghĩa là tốt hơn để sử dụng khóa, thay vì đồng bộ hóa đối tượng, cái sau là một dạng đơn giản hóa của cái trước.
dùng1914692

Tôi không hiểu làm thế nào điều này hoạt động. Nếu luồng ađang chờ trên đối tượng, synchronised(object)làm thế nào một luồng khác có thể đi qua synchronized(object)để gọi object.notifyAll()? Trong chương trình của tôi, mọi thứ chỉ bị kẹt trên synchronozedcác khối.
Tomáš Zato - Phục hồi Monica

@ TomášZato chủ đề đầu tiên gọi object.wait()hiệu quả mở khóa trên đối tượng đó. Khi luồng thứ hai "thoát" khối được đồng bộ hóa của nó, thì các đối tượng khác được giải phóng khỏi waitphương thức và lấy lại khóa tại thời điểm đó.
rogerdpack

140

Sử dụng CountDownLatch với bộ đếm là 1.

CountDownLatch latch = new CountDownLatch(1);

Bây giờ trong chủ đề ứng dụng làm-

latch.await();

Trong luồng db, sau khi bạn hoàn thành, hãy làm -

latch.countDown();

4
Tôi thực sự thích giải pháp đó vì sự đơn giản của nó, mặc dù có thể khó nắm bắt ý nghĩa của mã ngay từ cái nhìn đầu tiên.
lethal-guitar

3
Việc sử dụng này đòi hỏi bạn phải làm lại chốt khi chúng đã sử dụng hết. Để có được cách sử dụng tương tự như Sự kiện có thể chờ trong Windows, bạn nên thử BooleanLatch hoặc CountDownLatch có thể đặt lại: docs.oracle.com/javase/7/docs/api/java/util/concản/locks/ của stackoverflow.com/questions / 6595835 / Hoài
phyatt

1
Xin chào, nếu lần đầu tiên tôi gọi một phương thức async mà nó dự kiến ​​sẽ kích hoạt một sự kiện như: 1) asyncFunc (); 2) chốt.await (); Sau đó, tôi đếm ngược trong chức năng xử lý sự kiện một khi nó được nhận. Làm cách nào tôi có thể chắc chắn rằng sự kiện sẽ không được xử lý TRƯỚC KHI chốt.await () được gọi? Tôi muốn ngăn chặn sự ưu tiên giữa dòng 1 và 2. Cảm ơn bạn.
NioX5199

1
Để tránh phải chờ đợi mãi trong trường hợp có lỗi, hãy đặt countDown()vào một finally{}khối
Daniel Alder

23

Yêu cầu ::

  1. Để chờ thực hiện chuỗi tiếp theo cho đến khi hoàn thành trước đó.
  2. Chủ đề tiếp theo không được bắt đầu cho đến khi chủ đề trước dừng lại, bất kể thời gian tiêu thụ.
  3. Nó phải đơn giản và dễ sử dụng.

Câu trả lời ::

@See java.util.concản.Future.get () doc.

Tương lai.get () Chờ nếu cần thiết để tính toán hoàn thành, và sau đó lấy kết quả của nó.

Công việc hoàn thành!! Xem ví dụ dưới đây

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

public class ThreadTest {

    public void print(String m) {
        System.out.println(m);
    }

    public class One implements Callable<Integer> {

        public Integer call() throws Exception {
            print("One...");
            Thread.sleep(6000);
            print("One!!");
            return 100;
        }
    }

    public class Two implements Callable<String> {

        public String call() throws Exception {
            print("Two...");
            Thread.sleep(1000);
            print("Two!!");
            return "Done";
        }
    }

    public class Three implements Callable<Boolean> {

        public Boolean call() throws Exception {
            print("Three...");
            Thread.sleep(2000);
            print("Three!!");
            return true;
        }
    }

    /**
     * @See java.util.concurrent.Future.get() doc
     *      <p>
     *      Waits if necessary for the computation to complete, and then
     *      retrieves its result.
     */
    @Test
    public void poolRun() throws InterruptedException, ExecutionException {
        int n = 3;
        // Build a fixed number of thread pool
        ExecutorService pool = Executors.newFixedThreadPool(n);
        // Wait until One finishes it's task.
        pool.submit(new One()).get();
        // Wait until Two finishes it's task.
        pool.submit(new Two()).get();
        // Wait until Three finishes it's task.
        pool.submit(new Three()).get();
        pool.shutdown();
    }
}

Đầu ra của chương trình này ::

One...
One!!
Two...
Two!!
Three...
Three!!

Bạn có thể thấy rằng mất 6sec trước khi hoàn thành nhiệm vụ của nó lớn hơn các luồng khác. Vì vậy, Future.get () đợi cho đến khi nhiệm vụ hoàn thành.

Nếu bạn không sử dụng tương lai.get () thì sẽ không chờ để kết thúc và thực hiện tiêu thụ thời gian dựa trên.

Chúc may mắn với Java đồng thời.


Cảm ơn bạn vì câu trả lời! Tôi đã sử dụng CountdownLatches, nhưng của bạn là một cách tiếp cận linh hoạt hơn nhiều.
Piskvor rời khỏi tòa nhà

9

Rất nhiều câu trả lời đúng nhưng không có ví dụ đơn giản .. Đây là một cách dễ dàng và đơn giản để sử dụng CountDownLatch:

//inside your currentThread.. lets call it Thread_Main
//1
final CountDownLatch latch = new CountDownLatch(1);

//2
// launch thread#2
new Thread(new Runnable() {
    @Override
    public void run() {
        //4
        //do your logic here in thread#2

        //then release the lock
        //5
        latch.countDown();
    }
}).start();

try {
    //3 this method will block the thread of latch untill its released later from thread#2
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//6
// You reach here after  latch.countDown() is called from thread#2

8
public class ThreadEvent {

    private final Object lock = new Object();

    public void signal() {
        synchronized (lock) {
            lock.notify();
        }
    }

    public void await() throws InterruptedException {
        synchronized (lock) {
            lock.wait();
        }
    }
}

Sử dụng lớp này như thế này sau đó:

Tạo một ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

Trong phương pháp này đang chờ kết quả:

resultsReady.await();

Và trong phương thức tạo kết quả sau khi tất cả kết quả đã được tạo:

resultsReady.signal();

BIÊN TẬP:

(Xin lỗi vì đã chỉnh sửa bài đăng này, nhưng mã này có điều kiện chủng tộc rất tệ và tôi không đủ uy tín để bình luận)

Bạn chỉ có thể sử dụng điều này nếu bạn chắc chắn 100% rằng tín hiệu () được gọi sau khi chờ đợi (). Đây là một lý do lớn tại sao bạn không thể sử dụng đối tượng Java như Windows Events.

Nếu mã chạy theo thứ tự này:

Thread 1: resultsReady.signal();
Thread 2: resultsReady.await();

sau đó luồng 2 sẽ đợi mãi . Điều này là do Object.notify () chỉ đánh thức một trong các luồng hiện đang chạy. Một chủ đề chờ đợi sau đó không được đánh thức. Điều này rất khác với cách tôi mong đợi các sự kiện hoạt động, trong đó một sự kiện được báo hiệu cho đến khi a) chờ đợi hoặc b) thiết lập lại rõ ràng.

Lưu ý: Hầu hết thời gian, bạn nên sử dụng notifyAll (), nhưng điều này không liên quan đến vấn đề "chờ mãi" ở trên.


7

Hãy thử lớp CountDownLatch trong java.util.concurrentgói, cung cấp các cơ chế đồng bộ hóa ở mức cao hơn, ít xảy ra lỗi hơn bất kỳ công cụ cấp thấp nào.


6

Bạn có thể làm điều đó bằng cách sử dụng một đối tượng Exchanger được chia sẻ giữa hai luồng:

private Exchanger<String> myDataExchanger = new Exchanger<String>();

// Wait for thread's output
String data;
try {
  data = myDataExchanger.exchange("");
} catch (InterruptedException e1) {
  // Handle Exceptions
}

Và trong chuỗi thứ hai:

try {
    myDataExchanger.exchange(data)
} catch (InterruptedException e) {

}

Như những người khác đã nói, đừng lấy mã này sao chép và chỉ sao chép-dán mã. Làm một số đọc trước.


4

Các Future giao diện từjava.lang.concurrent gói phần mềm được thiết kế để cung cấp quyền truy cập vào kết quả tính toán trong thread khác.

Hãy xem FutureTaskExecutorService để biết cách thực hiện kiểu này.

Tôi thực sự khuyên bạn nên đọc Java đồng thời trong thực tế cho bất kỳ ai quan tâm đến đồng thời và đa luồng. Nó rõ ràng tập trung vào Java, nhưng cũng có nhiều thịt cho bất kỳ ai làm việc trong các ngôn ngữ khác.


2

Nếu bạn muốn một cái gì đó nhanh chóng và bẩn thỉu, bạn có thể chỉ cần thêm một cuộc gọi Thread.s ngủ () trong vòng lặp while của bạn. Nếu thư viện cơ sở dữ liệu là thứ bạn không thể thay đổi, thì thực sự không có giải pháp dễ dàng nào khác. Thăm dò cơ sở dữ liệu cho đến khi sẵn sàng với thời gian chờ đợi sẽ không giết chết hiệu suất.

while (!dbthread.isReady()) {
  Thread.sleep(250);
}

Hầu như một cái gì đó mà bạn có thể gọi mã thanh lịch, nhưng hoàn thành công việc.

Trong trường hợp bạn có thể sửa đổi mã cơ sở dữ liệu, sau đó sử dụng một mutex như đề xuất trong các câu trả lời khác là tốt hơn.


3
Điều này là khá nhiều chỉ bận rộn chờ đợi. Sử dụng các cấu trúc từ các gói produc.conc hiện của Java 5 sẽ là cách tốt nhất.stackoverflow.com/questions/289434/ đối với tôi là giải pháp tốt nhất hiện nay.
Cem Catikkas

Nó đang bận chờ đợi, nhưng nếu nó chỉ cần thiết ở nơi đặc biệt này và nếu không có quyền truy cập vào thư viện db, bạn có thể làm gì khác? Chờ đợi bận rộn không nhất thiết là xấu xa
Mario Ortegón

2

Điều này áp dụng cho tất cả các ngôn ngữ:

Bạn muốn có một mô hình sự kiện / người nghe. Bạn tạo một người nghe để chờ đợi một sự kiện cụ thể. Sự kiện này sẽ được tạo (hoặc báo hiệu) trong luồng công nhân của bạn. Điều này sẽ chặn luồng cho đến khi nhận được tín hiệu thay vì liên tục bỏ phiếu để xem có đáp ứng điều kiện nào không, giống như giải pháp bạn hiện có.

Tình huống của bạn là một trong những nguyên nhân phổ biến nhất gây ra bế tắc - đảm bảo bạn báo hiệu cho luồng khác bất kể lỗi có thể xảy ra. Ví dụ- nếu ứng dụng của bạn đưa ra một ngoại lệ - và không bao giờ gọi phương thức để báo hiệu cho cái khác rằng mọi thứ đã hoàn thành. Điều này sẽ làm cho nó để các chủ đề khác không bao giờ 'thức dậy'.

Tôi đề nghị bạn nên xem xét các khái niệm sử dụng các sự kiện và xử lý sự kiện để hiểu rõ hơn về mô hình này trước khi thực hiện trường hợp của bạn.

Ngoài ra, bạn có thể sử dụng lệnh gọi chức năng chặn bằng cách sử dụng mutex- điều này sẽ khiến luồng chờ tài nguyên được miễn phí. Để làm điều này, bạn cần đồng bộ hóa luồng tốt - chẳng hạn như:

Thread-A Locks lock-a
Run thread-B
Thread-B waits for lock-a
Thread-A unlocks lock-a (causing Thread-B to continue)
Thread-A waits for lock-b 
Thread-B completes and unlocks lock-b

2

Bạn có thể đọc từ một hàng đợi chặn trong một luồng và viết cho nó trong một luồng khác.


1

Từ

  1. join() đã được loại trừ
  2. bạn đã sử dụng CountDownLatch
  3. Future.get () đã được đề xuất bởi các chuyên gia khác,

Bạn có thể xem xét các lựa chọn thay thế khác:

  1. invokeTất cả từExecutorService

    invokeAll(Collection<? extends Callable<T>> tasks)

    Thực hiện các nhiệm vụ nhất định, trả về một danh sách các Tương lai giữ trạng thái và kết quả của chúng khi tất cả hoàn thành.

  2. ForkJoinPool hoặc newWorkStealsPool từ Executors(kể từ khi phát hành Java 8)

    Tạo nhóm luồng đánh cắp công việc bằng cách sử dụng tất cả các bộ xử lý có sẵn làm mức độ song song đích của nó.


-1

nhập mô tả hình ảnh ở đây

Ý tưởng này có thể áp dụng?. Nếu bạn sử dụng CountdownLatches hoặc Semaphores hoạt động hoàn hảo nhưng nếu bạn đang tìm kiếm câu trả lời dễ nhất cho một cuộc phỏng vấn tôi nghĩ rằng điều này có thể áp dụng.


1
Làm thế nào là ổn cho một cuộc phỏng vấn nhưng không phải cho mã thực tế?
Piskvor rời khỏi tòa nhà

Bởi vì trong trường hợp này là chạy tuần tự người ta chờ người khác. Giải pháp tốt nhất có thể là sử dụng Semaphores vì ​​sử dụng CountdownLatches là câu trả lời tốt nhất được đưa ra ở đây, Thread không bao giờ đi ngủ có nghĩa là sử dụng chu kỳ CPU.
Franco

Nhưng vấn đề không phải là "chạy chúng tuần tự." Tôi sẽ chỉnh sửa câu hỏi để làm cho vấn đề này rõ ràng hơn: Chủ đề GUI chờ cho đến khi cơ sở dữ liệu sẵn sàng và sau đó cả hai chạy đồng thời cho phần còn lại của ứng dụng: Chủ đề GUI gửi lệnh đến luồng DB và đọc kết quả. (Và một lần nữa: điểm mã nào có thể được sử dụng trong một cuộc phỏng vấn nhưng không phải là mã thực tế? Hầu hết những người phỏng vấn kỹ thuật tôi từng gặp đều có nền tảng về mã và sẽ hỏi cùng một câu hỏi; cộng với tôi cần điều này cho một ứng dụng thực tế Lúc đó tôi đang viết, không phải để chùn bước qua bài tập về nhà)
Piskvor rời khỏi tòa nhà

1
Vì thế. Đây là một vấn đề người tiêu dùng sản xuất bằng cách sử dụng semaphores. Tôi sẽ cố gắng làm một ví dụ
Franco

Tôi đã tạo một dự án github.com/francoj22/Sem ProducterConsumer / blob / master / src / com / . Nó hoạt động tốt.
Franco
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.