Java编程之多线程&并发编程(下)

6. Swing应用中的多线程问题

Swing应用运行于多个线程之上。特别是三类线程:
1.起始线程,或主线程,在main()方法中运行,启动GUI的构建和应用的退出;
2.EDT线程;
3.一些处理计算密集型任务及IO的后台线程。

所有的事件处理,绘制和界面刷新代码在单一线程(EDT)中进行,这是为了确保事件处理器在下个处理器启动之前完成执行,且绘制不被事件打断。如果因计算密集型任务造成EDT饥饿,用户界面将会挂起,程序对用户交互无响应。"理想情况下,任何需要花费30-100 ms任务不应该在EDT上运行。否则用户将感觉到输入和UI应答之间的停顿。

再者,所有访问GUI组件的代码应该在EDT上运行,因为这些组件中很多不能确保线程安全,而从相同线程中访问他们能避免多线程问题。

总之,
1.耗时和阻塞IO任务不应在EDT上运行,以避免造成EDT饥饿,进而影响用户交互的事件触发及界面刷新;
2.为了线程安全,Swing组件应仅在EDT上访问。

6.1 javax.Swing.SwingUtilities.invokeLater() 和invokeAndWait()

方法invokeLater(Runnable)和 invokeAndWait(Runnable)协调EDT中的Runnable任务。

为避免主线程(运行main()方法)和EDT之间线程问题,推荐使用javax.swing.SwingUtilities.invokeLater(Runnable)在EDT上创建GUI组件,如:

/** The entry main() method */
public static void main(String args[]) {
    // Run the GUI codes on the event-dispatching thread for thread-safety
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            JFrame frame = new JFrame("My Swing Application");
            frame.setContentPane(new MyMainPanel());
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.pack();
            frame.setLocationRelativeTo(null); // center on screen
            frame.setVisible(true);            // show it
        }
    });
}

静态方法SwingUtilities.invokelater()以Runnable对象(作为匿名内部类执行)为参数,并在EDT上制定任务(具体在run()方法中)。可以从任何线程中调用invokeLater()来要求EDT执行特定代码,这些代码可以写在Runnable参数的run()方法中。invokeLater()会立即返回,而不等待EDT执行这些代码。

如果有需要可以使用invokeAndWait(), 这方法会等待EDT执行指定代码才返回。对于applet,推荐(在init())通过invokeAndWait()运行GUI创建代码。这是为了避免init()在GUI创建完成之前退出造成问题。如:

public class SwingTemplateJApplet extends JApplet {
    /** init() to setup the GUI components */
    @Override
    public void init() {
        // Run GUI codes in the Event-Dispatching thread for thread safety
        try {
            // Use invokeAndWait() to ensure that init() exits after GUI construction
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    // Set the content-pane of JApplet to an instance of main JPanel
                    setContentPane(new SwingTemplateJPanel());
                }
            });
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

也可用java.awt.EventQueue.invokeLater()代替javax.swing.SwingUtilities.invokeLater(),javax.swing.SwingUtilities.invokeLater()是 java.awt.EventQueue.invokeLater()的进一步封装。

通过之前例子的追踪可以发现如果没有使用SwingUtilities.invokeLater(),EDT在setVisible()方法之后启动。另一方面,invokerLater()启动EDT。

6.2 javax.swing.Timer (JDK 1.2)

如果需要在一定时间后或一段频繁时间间隔内更新组件,使用时间类如 javax.swing.Timer(JDK 1.2)或java.util.Timer(JDK 1. 3)。

对于javax.swing.Timer,阅读"animation using javax.swing.Timer"。

6.3 javax.swing.SwingWorker<T,V> (JDK 1.6)

如之前提到过的,在Swing应用中:

  1. 计算密集型任务不应在EDT上运行,以避免EDT在处理事件和重绘中出现饥饿;
  2. 为了线程安全,Swing组件应仅在EDT上访问。

类javax.swing.SwingWorkder<T,V> 帮助管理唯一的EDT和几个后台工作者线程之间的交互。它可以用来协调后台线程中的计算密集型任务并返回EDT中产生的最终结果或中间结果。

定义 SwingWorker 类如:public abstract class SwingWorker<T,V> implements RunnableFuture

SwingWorker<T,V>是带两种类型参数的抽象类:T 指方法doInBackground()和get()最终结果类型,而V指 方法publish()和process()中间结果类型。

RunnableFuture 接口是两个接口的结合:Runnable 和Future。接口Runnable 声明了抽象方法run();而 Future 声明 get(), cancel(), isDone(), 和isCancelled()。

制定后台任务

protected abstract T doInBackground() throws Exception
    // Do this task in a background thread
protected void done()
    // Executes on the Event-Dispatching thread after the doInBackground() method finishes.
public final T get() throws InterruptedException, ExecutionException
    // Waits for doInBackground() to complete and gets the result.
    // Calling get() on the Event-Dispatching thread blocks all events, including repaints,
    //  until the SwingWorker completes.
public final void execute()
    // Schedules this SwingWorker for execution on one of the worker thread.
public final boolean cancel(boolean mayInterruptIfRunning)
    // Attempts to cancel execution of this task.
public final boolean isDone()
    // Returns true if this task has completed (normally or exception)
public final boolean isCancelled()
    // Returns true if this task was cancelled before it completed normally

为在工作者线程中制定一项任务,扩展 SwingWorker<T,V>子类并重写:

  1. doInBackground()指定执行任务,在一个工作者线程中制定并返回类型为T的结果;
  2. done()方法在 doInBackground()完成后在EDT中运行,用get()方法取得doInBackground()结果 (类型 T)。
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
 
/** Test SwingWorker on the counter application with a compute-intensive task */
@SuppressWarnings("serial")
public class SwingWorkerCounter extends JPanel {
    // For counter
    private JTextField tfCount;
    private int count = 0;
    // For SwingWorker
    JButton btnStartWorker;   // to start the worker
    private JLabel lblWorker; // for displaying the result
 
    /** Constructor to setup the GUI components */
    public SwingWorkerCounter () {
        setLayout(new FlowLayout());
 
        add(new JLabel("Counter"));
        tfCount = new JTextField("0", 10);
        tfCount.setEditable(false);
        add(tfCount);
 
        JButton btnCount = new JButton("Count");
        add(btnCount);
        btnCount.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ++count;
                tfCount.setText(count + "");
            }
        });
 
        /** Create a SwingWorker instance to run a compute-intensive task 
            Final result is String, no intermediate result (Void) */
        final SwingWorker<String, Void> worker = new SwingWorker<String, Void>() {
            /** Schedule a compute-intensive task in a background thread */
            @Override
            protected String doInBackground() throws Exception {
                // Sum from 1 to a large n
                long sum = 0;
                for (int number = 1; number < 1000000000; ++number) {
                    sum += number;
                }
                return sum + "";
            }
 
            /** Run in event-dispatching thread after doInBackground() completes */
            @Override
            protected void done() {
                try {
                    // Use get() to get the result of doInBackground()
                    String result = get();
                    // Display the result in the label (run in EDT)
                    lblWorker.setText("Result is " + result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        };
 
        btnStartWorker = new JButton("Start Worker");
        add(btnStartWorker);
        btnStartWorker.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                worker.execute();                 // start the worker thread
                lblWorker.setText("  Running...");
                btnStartWorker.setEnabled(false); // Each instance of SwingWorker run once
            }
        });
        lblWorker = new JLabel("  Not started...");
        add(lblWorker);
 
    }
 
    /** The entry main() method */
    public static void main(String[] args) {
        // Run the GUI construction in the Event-Dispatching thread for thread-safety
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("SwingWorker Test");
                frame.setContentPane(new SwingWorkerCounter());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(300, 150);
                frame.setVisible(true);
            }
        });
    }
}

SwingWorker的实例设计只能运行一次且不能重新启动,需要创建新实例重新执行任务。

发布并处理中间结果

不一定在done()中处理最终结果, 如有需要可以publish()并 process()中间结果。

@SafeVarargs
protected final void publish(V... chunks)
    // Sends data chunks to the process(java.util.List<V>) method.
    // This method shall be called inside the doInBackground() to deliver intermediate results
    //  for processing on the Event-Dispatching thread inside the process() method.
protected void process(List<V> chunks)
    // Receives data chunks from publish() asynchronously on the Event-Dispatching thread.

在doInBackground()中, 用publish(V...) 发布一个或更多类型V中间结果。重写process(List<V>)方法来处理List<V>中公布的结果,process()方法运行在EDT中。

import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
 
/** Test SwingWorker on the counter application with a compute-intensive task */
@SuppressWarnings("serial")
public class SwingWorkerCounterIntermediateResult extends JPanel {
    // For counter
    private JTextField tfCount;
    private int count = 0;
    // For SwingWorker
    JButton btnStartWorker;   // to start the worker
    private JLabel lblWorker; // for displaying the result
 
    /** Constructor to setup the GUI components */
    public SwingWorkerCounterIntermediateResult () {
        setLayout(new FlowLayout());
 
        add(new JLabel("Counter"));
        tfCount = new JTextField("0", 10);
        tfCount.setEditable(false);
        add(tfCount);
 
        JButton btnCount = new JButton("Count");
        add(btnCount);
        btnCount.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ++count;
                tfCount.setText(count + "");
            }
        });
 
        /** Create a SwingWorker instance to run a compute-intensive task */
        final SwingWorker<String, String> worker = new SwingWorker<String, String>() {
 
            /** Schedule a compute-intensive task in a background thread */
            @Override
            protected String doInBackground() throws Exception {
                long sum = 0;
                for (int number = 0; number < 10000000; ++number) {
                    sum += number;
                    publish(sum + ""); // Send "every" intermediate result to process()
                                  // You might not publish every intermediate result
                }
                return sum + "";
            }
 
            /** Run in event-dispatching thread after doInBackground() completes */
            @Override
            protected void done() {
                try {
                    // Use get() to get the result of doInBackground()
                    String finalResult = get();
                    // Display the result in the label (run in EDT)
                    lblWorker.setText("Final Result is " + finalResult);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
 
            /** Run in event-dispatching thread to process intermediate results
                send from publish(). */
            @Override
            protected void process(java.util.List<String> chunks) {
                // Get the latest result from the list
                String latestResult = chunks.get(chunks.size() - 1);
                lblWorker.setText("Result is " + latestResult);
            }
        };
 
        btnStartWorker = new JButton("Start Worker");
        add(btnStartWorker);
        btnStartWorker.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                worker.execute();  // start the worker thread
                lblWorker.setText("  Running...");
                btnStartWorker.setEnabled(false);  // SwingWorker can only run once
            }
        });
        lblWorker = new JLabel("  Not started...");
        add(lblWorker);
 
    }
 
    /** The entry main() method */
    public static void main(String[] args) {
        // Run the GUI construction in the Event-Dispatching thread for thread-safety
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("SwingWorker Test");
                frame.setContentPane(new SwingWorkerCounterIntermediateResult());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(300, 150);
                frame.setVisible(true);
            }
        });
    }
}

属性变化事件

doInBackground()发送PropertyChangeEvent给其所有的PropertyChangeListeners 关于边界属性变化。有两个边界属性:"state" 和"progress"。"state"在封装枚举类型SwingWorker.StateValue中定义,StateValue取值为PENDING (SwingWorker 实例创建), START (doInBackground启动)和DONE(doInBackground 完成) 。"progress" 是一个整数型,取值范围从0到100。可以在doInBackground()中 通过setProgress()方法改变progress值来发送PropertyChangeEvent给其所有的PropertyChangeListeners。

例子

在本例 doInBackground()方法内,调用setProgess()来改变progress边界特征值(0-100之间),然后发送PropertyChangeEvent。用SwingWorker定义并注册 PropertyChangeListener , 在进度条显示progress值。事件处理器在EDT中运行。

import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
 
/** Test SwingWorker on the counter application with a compute-intensive task */
@SuppressWarnings("serial")
public class SwingWorkerCounterProgress extends JPanel {
    // For counter
    private JTextField tfCount;
    private int count = 0;
    // For SwingWorker
    JButton btnStartWorker;   // to start the worker
    private JLabel lblWorker; // for displaying the result
    JProgressBar pbWorker;    // progress bar for the worker task
 
    /** Constructor to setup the GUI components */
    public SwingWorkerCounterProgress () {
        setLayout(new FlowLayout());
 
        add(new JLabel("Counter"));
        tfCount = new JTextField("0", 10);
        tfCount.setEditable(false);
        add(tfCount);
 
        JButton btnCount = new JButton("Count");
        add(btnCount);
        btnCount.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ++count;
                tfCount.setText(count + "");
            }
        });
 
        /** Create a SwingWorker instance to run a compute-intensive task */
        final SwingWorker<String, String> worker = new SwingWorker<String, String>() {
 
            /** Schedule a compute-intensive task in a background thread */
            @Override
            protected String doInBackground() throws Exception {
                long sum = 0;
                int maxNumber = 10000000;
                for (int number = 0; number < maxNumber; ++number) {
                    sum += number;
                    publish(sum + ""); // send intermediate result to process()
                    // Fire PropertyChangeEvent for the bound-property "progress"
                    setProgress(100 * (number + 1) / maxNumber);
                }
                return sum + "";
            }
 
            /** Run in event-dispatching thread after doInBackground() completes */
            @Override
            protected void done() {
                try {
                    // Use get() to get the result of doInBackground()
                    String finalResult = get();
                    // Display the result in the label (run in EDT)
                    lblWorker.setText("Final Result is " + finalResult);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
 
            /** Run in event-dispatching thread to process intermediate results
                send from publish(). */
            @Override
            protected void process(java.util.List<String> chunks) {
                // Get the latest result from the list
                String latestResult = chunks.get(chunks.size() - 1);
                lblWorker.setText("Result is " + latestResult);
            }
        };
 
        /** Event handler for the PropertyChangeEvent of property "progress" */
        worker.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equals("progress")) {  // check the property name
                    pbWorker.setValue((Integer)evt.getNewValue());  // update progress bar
                }
            }
        });
 
        btnStartWorker = new JButton("Start Worker");
        add(btnStartWorker);
        btnStartWorker.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                worker.execute();  // start the worker thread
                lblWorker.setText("  Running...");
                btnStartWorker.setEnabled(false);  // SwingWorker can only run once
            }
        });
        lblWorker = new JLabel("  Not started...");
        add(lblWorker);
        pbWorker = new JProgressBar();
        add(pbWorker);
    }
 
    /** The entry main() method */
    public static void main(String[] args) {
        // Run the GUI construction in the Event-Dispatching thread for thread-safety
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("SwingWorker Test");
                frame.setContentPane(new SwingWorkerCounterProgress());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(300, 150);
                frame.setVisible(true);
            }
        });
    }
}

6.4 总结

线程对构建响应性GUI很有必要,有一些应该使用新线程的典型情形:

  • 在主线程中创建一新线程来处理耗时初始化任务(如:磁盘I/O)以便GUI加载更快;
  • 在EDT中创建一新线程来处理耗时任务(在事件处理器中)以便GUI保持响应;
  • 用计时器处理重复任务,在一定时间间隔或延时一段时间执行;
  • 如果操作需要等待其他线程或程序的信息,创建一个新线程。

此外,计算密集型线程必须协作并将控制交与其他线程。

7. Thread Pool, Executor, Callable/Future (JDK 1.5)

线程池支持类在JDK1.5 java.lang.concurrent包中引入:


7.1 线程池(Thread Pool)

线程池是一个能执行任务线程的管理集合。当使用线程池执行大量任务时,效率会因线程循环执行任务而提高,这会降低每个任务调用开销。

可以实现接口ExecutorService来使用线程池,如ThreadPoolExecutor 或ScheduledThreadPoolExecutor,而在Executors 类中提供了更多便捷工厂方法,列举如下:

  • Executors.newSingleThreadExecutor(): 创建单一后台线程;
  • Executors.newFixedThreadPool(int numThreads): 创建固定大小的线程池;
  • Executors.newCachedThreadPool(): 创建带自动线程回收机制的无界线程池。

使用线程池的步骤:

  1. 写一个实现Runnable接口的工作者线程类,run()方法制定运行线程的行为;
  2. 用Executors 类提供的工厂方法创建线程池(ExecutorService),该线程池可以只有一个线程,固定数量线程或无限数量线程;
  3. 创建工作者线程类实例。用线程池的方法execute(Runnable r)来添加Runnable 任务到线程池,该任务将被协调并在有空闲线程时得到执行。

7.2 接口java.util.concurrent.Executor

Executor对象能执行提交的Runnable 任务,该接口声明了下面抽象方法:
public void execute(Runnable r)

在未来某个时候执行给定任务,任务因Executor执行情况可能在新线程,线程池或当前线程中执行。

7.3 接口java.util.concurrent.ExecutorService

接口ExecutorService 声明很多抽象方法,其中比较重要的有:

public void shutdown();
    // Initiates an orderly shutdown of the thread pool.
    // The previously executed/submitted tasks are allowed to complete,
    // but no new tasks will be scheduled.
public <T> Future<T> submit(Callable<T> task);
    // Submit or schedule the callable task for execution, which returns a Future object.

7.4 类java.util.concurrent.Executors

类Executors 提供创建Executor对象的工厂方法。如:

static ExecutorService newSingleThreadExecutor()
static ExecutorService newFixedThreadPool(int nThreads)
static ExecutorService newCachedThreadPool()
static ScheduledExecutorService newSingleThreadScheduledExecutor()
static ScheduledExecutorService newScheduledThreadPool(int size)
public class WorkerThread implements Runnable {
    private int workerNumber;

    WorkerThread(int workerNumber) {
        this.workerNumber = workerNumber;
    }

    public void run() {
        // The thread simply prints 1 to 5
        for (int i = 1; i <= 5; ++i) {
            System.out.printf("Worker %d: %d\n", workerNumber, i);
            try {
                // sleep for 0 to 0.5 second
                Thread.sleep((int)(Math.random() * 500));
            } catch (InterruptedException e) {}
        }
    }
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTest {
    public static void main(String[] args) {
        int numWorkers = Integer.parseInt(args[0]);
        int threadPoolSize = Integer.parseInt(args[1]);

        ExecutorService pool = 
                Executors.newFixedThreadPool(threadPoolSize);
        WorkerThread[] workers = new WorkerThread[numWorkers];
        for (int i = 0; i < numWorkers; ++i) {
            workers[i] = new WorkerThread(i+1);
            pool.execute(workers[i]);
        }
        pool.shutdown();
    }
}
//2 threads, 5 workers
> java ThreadPoolTest 5 2
Worker 1: 1
Worker 2: 1
Worker 1: 2
Worker 1: 3
Worker 2: 2
Worker 1: 4
Worker 2: 3
Worker 1: 5
Worker 3: 1
Worker 3: 2
Worker 2: 4
Worker 3: 3
Worker 2: 5
Worker 3: 4
Worker 3: 5
Worker 4: 1
Worker 4: 2
Worker 5: 1
Worker 4: 3
Worker 5: 2
Worker 5: 3
Worker 4: 4
Worker 5: 4
Worker 5: 5
Worker 4: 5

首先安排工作者1和2用线程池中的两个线程执行,接着是工作者3,4和5。占用线程任务完成后,线程返回线程池,然后安排另外一个任务并开始执行。

可以调用pool.shutdown()来关闭线程池中所有线程。

7.5 接口java.util.concurrent.Callable<V> 和Future<V>

Callable 类似于Runnable,不同的是 Callable提供方法返回结果或Exception到相应的线程中。Callable声明了抽象方法call()(不同于Runnable中的run())。
public V call() // Call() returns a result of type <V>, or throws an exception if unable to do so.

在线程池中,使用submit(Callable r)而非execute(Runnable r),这会返回一个Future<V> 对象(在ExecutorService接口中声明)。当需要结果时,可以对Future对象调用get()方法加以获取。结果一旦完成便会返回,否则当前线程一直阻塞直到获取结果。

接口Future<V> 声明了下面的抽象方法:

V get()           // wait if necessary, retrieve result
V get(long timeout, TimeUnit unit)
boolean cancel(boolean mayInterruptIfRunning)
boolean isCancelled()
boolean isDone()  // return true if this task completed
import java.util.concurrent.Callable;

public class CallableWorkerThread implements Callable<String> {
    private int workerNumber;

    CallableWorkerThread(int workerNumber) {
        this.workerNumber = workerNumber;
    }

    public String call() {    // use call() instead of run()
        for (int i = 1; i <= 5; ++i) {    // just print 1 to 5
            System.out.printf("Worker %d: %d\n", workerNumber, i);
            try {
                Thread.sleep((int)(Math.random() * 1000));
            } catch (InterruptedException e) {}
        }
        return "worker " + workerNumber;
    }
}
import java.util.concurrent.*;

public class CallableThreadPoolTest {
    public static void main(String[] args) {
        int numWorkers = Integer.parseInt(args[0]);

        ExecutorService pool = Executors.newCachedThreadPool();
        CallableWorkerThread workers[] = new CallableWorkerThread[numWorkers];
        Future[] futures = new Future[numWorkers];

        for (int i = 0; i < numWorkers; ++i) {
            workers[i] = new CallableWorkerThread(i + 1);
            futures[i] = pool.submit(workers[i]);
        }
        for (int i = 0; i < numWorkers; ++i) {
            try {
                System.out.println(futures[i].get() + " ended");
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            } catch (ExecutionException ex) {
                ex.printStackTrace();
            }
        }
    }
}
三个工作者线程的输出:
> java CallableThreadPoolTest 3
Worker 1: 1
Worker 3: 1
Worker 2: 1
Worker 3: 2
Worker 1: 2
Worker 2: 2
Worker 2: 3
Worker 2: 4
Worker 2: 5
Worker 1: 3
Worker 3: 3
Worker 3: 4
Worker 3: 5
Worker 1: 4
Worker 1: 5
worker 1 ended
worker 2 ended
worker 3 ended

7.6 JDK 1.5中其他新线程特性

  • Lock
  • ReadWriteLock
  • Semaphores
  • Atomics
  • Blocking Queue

Java参考及资源链接

更多参考文献及资源

  1. Swing Tutorial's "Concurrency in Swing".
  2. John O'Conner, "Improve Application Performance With SwingWorker in Java SE 6" @ http://java.sun.com/developer/technicalArticles/javase/swingworker/.

翻译自:
https://www3.ntu.edu.sg/home/ehchua/programming/java/j5e_multithreading.html

Java编程之多线程&并发编程(上)
Java编程之多线程&并发编程(中)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • layout: posttitle: 《Java并发编程的艺术》笔记categories: Javaexcerpt...
    xiaogmail阅读 5,787评论 1 19
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 1. 介绍-多任务和多线程 Java 支持单线程和多线程, 单线程程序有一个单一入口(main()函数)及单一出...
    vancent阅读 1,002评论 0 3
  • 今天孤身一人来到了海边,远望,海与天空的交线虚虚实实。我踩在沙滩上,沙子的触感并没有想象的柔软细腻。海浪一朵朵拍来...
    绫绡阅读 300评论 0 0
  • 美丽的女人都懂得爱惜自己,如鸟爱惜自己的羽毛。而一个男人也应该爱惜女人,就如爱惜自己的羽毛。 ——题记。 一、隐秘...
    景标的杨阅读 550评论 0 0