30/09/2018, 19:51

Hỏi về đa luồng trong java

Chào các bạn, chomình hỏi chút về đa luồng trong java. Mình có 1 giao diện swing, trên đó có duy nhất 1 nút Restart, khi bấm nút thì thực hiện việc setText cho 1 label. Bên cạnh đó mình có 1 luồng in ra số từ 1 đến 10.000. như sau

public class ThreadCheckVer implements Runnable{
@Override
public void run() {
	
	for (int i = 0; i < 1000000; i++) {
		System.out.println(i);
	}
} }

Khi chạy giao diện swing mình đồng thời cho thread này chạy

public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					restartDemo frame = new restartDemo();
					frame.setVisible(true);
					//setdata();
					tcv = new Thread(new ThreadCheckVer());
					tcv.start();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}`

Sự kiên cho nút trên giao diện swing

`btnRestart.addActionListener(new ActionListener() {
  	public void actionPerformed(ActionEvent arg0) {
			int opt = JOptionPane.showConfirmDialog(null,"Are you sure?", "Confirm", JOptionPane.YES_NO_OPTION);
			if (opt == JOptionPane.YES_OPTION) {
                                lblstatus.setText("Restarting...");
				try {
					tcv.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				lblstatus.setText("Restarting...");
			}	
		}
	});`

Vấn đề ở đây là mình mong muốn khi bấm nút thì lblstatus sẽ hiện “Restarting…” trong khi chờ thread in số xong việc, nhưng thực tế thì nó đang chạy ngược lại, in xong số rồi mới setText cho lblstatus.

Bạn nào có thể giải thích giúp mình vì sao lại như vậy không? Tks a lot.

Phan Hoàng viết 22:05 ngày 30/09/2018

Rất đơn giản vì Swing chạy trên 1 thread gọi là Event Dispatching Thread (vẽ vời, init các UI về đồ họa và là thread chạy nặng nhất). Khi bạn gọi lệnh tcv.join, thread EDT sẽ phải đợi thread này kết thúc thì nó mới chạy tiếp cập nhật lại giao diện (do đó, nó sẽ block cái thread EDT và app trở thành non-reponsive).

Bỏ cái tcv.join đi là app sẽ reponsive ngay.

Tuy nhiên, mình chưa hiểu context nên không biết bạn có cần nghe xem thread đếm số đã xong chưa? Nếu cần nghe thì bạn lại nhét vào EventQueue và nghe (lúc đó app vẫn reponsive được, khi thread tcv xong, nó sẽ gửi event sang cho EDT để cập nhật lại)

Davit lượt viết 21:59 ngày 30/09/2018

Kịch bản của mình như này: Giao diện swing và thread tcv chạy lên đồng thời -> Bấm nút Restart để khởi động lại app -> Sau khi bấm nút thì app sẽ chờ cho thread tcv chạy xong mới restart, đồng thời ngay sau thời điểm bấm nút thì label sẽ đc setText “Restarting…” -> Sau khi thread tcv xong việc thì khởi động lại app.

Nếu bỏ tcv.join() thì không được vì app khởi động lại trong khi thread tcv vẫn đang chạy, mình muốn mọi thread con phải xong hết mới khởi động lại.

Như vậy thì nếu dùng tcv.join() thì hoặc thread main hoặc thread EDT sẽ bị block không vẽ lại được giao diện, bạn có phương án nào để xử lý việc này không?

Phan Hoàng viết 22:07 ngày 30/09/2018

Hi, giải pháp thì có 1 số:

  • Thay vì set setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); bạn chuyển sang EXIT_ON_DISPOSE, khi đó nếu muốn tắt và khởi động lại app, thì thằng Swing Thread (EDT) sẽ chờ tới khi các Windows/Thread phải được tắt hết.
  • Cái EDT nó xử lý cả handler click nên bạn mà viết vào onClick rõ ràng sẽ bị block app và trở thành non-reponsive. Có thể có 1 số cách vượt qua mà mình có thể nghĩ tới như:
  • Trick nó: tức là bạn bấm vào nút, đừng cho join trên EDT mà hãy cho join trên main thread. Viết vào trong main ý (đừng có nhét vào EventQueue vì EQ là đã xử lý trên EDT rồi)
  • Sử dụng Observer Pattern để thực hiện việc gửi message sang EDT cập nhật giao diện. Sau đó khi msg = 100% thì restart lại (^^)

Mình có code thử cách 1 (chưa work vì Frame được init trên EDT) nên sử dụng cách 2 (mình dùng luôn SwingWorker cho nhanh ^^). Bạn thử tham khảo xem có hướng gì được không?

GitHub

laisotrym/POC

Contribute to laisotrym/POC development by creating an account on GitHub.

Davit lượt viết 21:56 ngày 30/09/2018

Để mình thử xem, tiện đây chomình hỏi thêm cái này chút: Vẫn là code trên, thay vì setText cho label, mình đưa ra 1 box confirm (JOptionPane.showConfirmDialog(…)) thì nó lại hiện ra ngay mà tại sao cái setText lại chờ đến khi thread xong mới có hiệu lực (vì hiện tại lúc này thread EDT đang blocked)

Davit lượt viết 22:01 ngày 30/09/2018

Thanks Hoàng nhé, mình thử dùng SwingWorker đã giải quyết được rồi. Tuy nhiên mình chưa hiểu rõ lắm, khi dùng swingworker như vậy thì nó chạy trên luồng nào? EDT hay main?

Phan Hoàng viết 21:58 ngày 30/09/2018

Nó chạy trên luồng riêng theo design pattern Observer / Message Bus đó ^^
Còn câu hỏi của bạn là tại sao Dialog hiện ra ngay thì mình cũng chưa trả lời được. ^^

Bình thường mình hiểu Dialog trong Java nó hoạt động thế này:

  • Khi gọi nó lên, nó sẽ block toàn bộ EDT (không cho vẽ vời gì cả (gọi là Modal Dialog)).
  • Do đó, khi gọi nó lên, nó block EDT trước cái thread của bạn -> sau đó bạn mới gọi join thì dialog đã bị dismiss/dispose rồi, do đó khi gọi join, EDT sẽ nghe message của thread đó (và bị block). Thực ra thì EDT luôn là thread bị kết thúc cuối cùng (không tính main nhé).

(Join là cơ chế xếp hàng.Ví dụ nếu bạn có 2 thread t1, t2 cùng join với EDT thì t1,t2 này chạy // với nhau, nhưng EDT buộc phải chờ cả 2 thằng này thì mới chạy tiếp được. Thường thì khi lập trình với Thread, nên dùng cơ chế wait/notify để làm synchronize hơn để tránh bị block)

Davit lượt viết 22:03 ngày 30/09/2018

Thanks vì những chia sẻ của bạn.

Bài liên quan
0