活动介绍

Java多线程编程全面解析

立即解锁
发布时间: 2025-08-18 00:08:22 阅读量: 2 订阅数: 9
### Java多线程编程全面解析 在现代软件开发中,多线程编程是一项至关重要的技术,它能显著提升程序的性能和响应能力。本文将深入探讨Java中的多线程编程,涵盖线程的基本概念、线程的创建与管理、线程同步、死锁问题以及在用户界面编程中的应用等多个方面。 #### 1. 多线程基础 在深入探讨多线程编程之前,我们需要先了解一些基本概念。多任务是指计算机系统能够同时运行多个程序的能力。在大多数情况下,这是通过操作系统的资源分配来实现的,使得每个程序看起来都在同时运行。多线程则是多任务的一种更细粒度的实现方式,它允许单个程序中的多个任务同时执行。 线程是程序中的一个执行单元,每个线程都有自己的执行路径和上下文。与多进程相比,线程共享相同的数据空间,这使得它们之间的通信更加高效,但也带来了一些挑战,例如数据竞争和死锁问题。 在Java中,创建和管理线程非常方便。Java平台内置了对多线程的支持,使得开发者可以轻松地创建和控制线程。下面我们通过一个简单的示例来演示如何在Java中创建和运行线程。 ```java // Example 1-1 Bounce.java import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.util.*; import javax.swing.*; /** * Shows an animated bouncing ball. */ public class Bounce { public static void main(String[] args) { JFrame frame = new BounceFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } /** * The frame with canvas and buttons. */ class BounceFrame extends JFrame { private BallCanvas canvas; public static final int WIDTH = 450; public static final int HEIGHT = 350; public BounceFrame() { setSize(WIDTH, HEIGHT); setTitle("Bounce"); Container contentPane = getContentPane(); canvas = new BallCanvas(); contentPane.add(canvas, BorderLayout.CENTER); JPanel buttonPanel = new JPanel(); addButton(buttonPanel, "Start", new ActionListener() { public void actionPerformed(ActionEvent ev) { addBall(); } }); addButton(buttonPanel, "Close", new ActionListener() { public void actionPerformed(ActionEvent ev) { System.exit(0); } }); contentPane.add(buttonPanel, BorderLayout.SOUTH); } public void addButton(Container c, String title, ActionListener listener) { JButton button = new JButton(title); c.add(button); button.addActionListener(listener); } public void addBall() { try { Ball b = new Ball(canvas); canvas.add(b); for (int i = 1; i <= 1000; i++) { b.move(); Thread.sleep(5); } } catch (InterruptedException exception) { } } } /** * The canvas that draws the balls. */ class BallCanvas extends JPanel { private ArrayList balls = new ArrayList(); public void add(Ball b) { balls.add(b); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; for (int i = 0; i < balls.size(); i++) { Ball b = (Ball) balls.get(i); b.draw(g2); } } } /** * A ball that moves and bounces off the edges of a component */ class Ball { private Component canvas; private static final int XSIZE = 15; private static final int YSIZE = 15; private int x = 0; private int y = 0; private int dx = 2; private int dy = 2; public Ball(Component c) { canvas = c; } public void draw(Graphics2D g2) { g2.fill(new Ellipse2D.Double(x, y, XSIZE, YSIZE)); } public void move() { x += dx; y += dy; if (x < 0) { x = 0; dx = -dx; } if (x + XSIZE >= canvas.getWidth()) { x = canvas.getWidth() - XSIZE; dx = -dx; } if (y < 0) { y = 0; dy = -dy; } if (y + YSIZE >= canvas.getHeight()) { y = canvas.getHeight() - YSIZE; dy = -dy; } canvas.paint(canvas.getGraphics()); } } ``` 在这个示例中,我们创建了一个简单的动画程序,模拟一个球在窗口中弹跳。当点击“Start”按钮时,球开始弹跳,但在球完成1000次移动之前,用户无法与程序进行其他交互。这是因为球的移动是在主线程中执行的,阻塞了用户界面的响应。 为了解决这个问题,我们可以将球的移动逻辑放在一个单独的线程中执行。这样,主线程就可以继续处理用户界面事件,从而提高程序的响应能力。 ```java // Example 1-2 BounceThread.java import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.util.*; import javax.swing.*; /** * Shows an animated bouncing ball running in a separate thread. */ public class BounceThread { public static void main(String[] args) { JFrame frame = new BounceFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } /** * The frame with canvas and buttons. */ class BounceFrame extends JFrame { private BallCanvas canvas; public static final int WIDTH = 450; public static final int HEIGHT = 350; public BounceFrame() { setSize(WIDTH, HEIGHT); setTitle("BounceThread"); Container contentPane = getContentPane(); canvas = new BallCanvas(); contentPane.add(canvas, BorderLayout.CENTER); JPanel buttonPanel = new JPanel(); addButton(buttonPanel, "Start", new ActionListener() { public void actionPerformed(ActionEvent ev) { addBall(); } }); addButton(buttonPanel, "Close", new ActionListener() { public void actionPerformed(ActionEvent ev) { System.exit(0); } }); contentPane.add(buttonPanel, BorderLayout.SOUTH); } public void addButton(Container c, String title, ActionListener listener) { JButton button = new JButton(title); c.add(button); button.addActionListener(listener); } public void addBall() { Ball b = new Ball(canvas); canvas.add(b); BallThread thread = new BallThread(b); thread.start(); } } /** * A thread that animates a bouncing ball. */ class BallThread extends Thread { private Ball b; public BallThread(Ball aBall) { b = aBall; } public void run() { try { for (int i = 1; i <= 1000; i++) { b.move(); sleep(5); } } catch (InterruptedException exception) { } } } /** * The canvas that draws the balls. */ class BallCanvas extends JPanel { private ArrayList balls = new ArrayList(); public void add(Ball b) { balls.add(b); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; for (int i = 0; i < balls.size(); i++) { Ball b = (Ball) balls.get(i); b.draw(g2); } } } /** * A ball that moves and bounces off the edges of a component */ class Ball { private Component canvas; private static final int XSIZE = 15; private static final int YSIZE = 15; private int x = 0; private int y = 0; private int dx = 2; private int dy = 2; public Ball(Component c) { canvas = c; } public void draw(Graphics2D g2) { g2.fill(new Ellipse2D.Double(x, y, XSIZE, YSIZE)); } public void move() { x += dx; y += dy; if (x < 0) { x = 0; dx = -dx; } if (x + XSIZE >= canvas.getWidth()) { x = canvas.getWidth() - XSIZE; dx = -dx; } if (y < 0) { y = 0; dy = -dy; } if (y + YSIZE >= canvas.getHeight()) { y = canvas.getHeight() - YSIZE; dy = -dy; } canvas.repaint(); } } ``` 在这个改进后的示例中,我们创建了一个`BallThread`类,继承自`Thread`类,并在`run`方法中实现了球的移动逻辑。当点击“Start”按钮时,我们创建一个`BallThread`对象并启动它,这样球的移动就会在一个单独的线程中执行,主线程可以继续处理用户界面事件。 #### 2. 线程状态与属性 在Java中,线程可以处于以下四种状态之一: - **New(新建)**:当使用`new`关键字创建一个线程对象时,线程处于新建状态。此时,线程还没有开始执行。 - **Runnable(可运行)**:当调用线程的`start`方法后,线程进入可运行状态。此时,线程可能正在运行,也可能正在等待操作系统分配CPU时间。 - **Blocked(阻塞)**:当线程遇到某些阻塞操作时,如调用`sleep`方法、等待输入输出操作完成或等待锁时,线程进入阻塞状态。 - **Dead(死亡)**:当线程的`run`方法执行完毕或抛出未捕获的异常时,线程进入死亡状态。 线程还有一些重要的属性,如优先级、守护线程和线程组。线程的优先级可以通过`setPriority`方法设置,范围从`MIN_PRIORITY`(1)到`MAX_PRIORITY`(10),默认优先级为`NORM_PRIORITY`(5)。守护线程是一种特殊的线程,当所有非守护线程结束时,守护线程会自动结束。线程组可以将多个线程组织在一起,方便统一管理。 以下是一个示例,演示如何设置线程的优先级和使用线程组: ```java import java.lang.Thread; public class ThreadPropertiesExample { public static void main(String[] args) { // 创建线程组 ThreadGroup group = new ThreadGroup("MyThreadGroup"); // 创建两个线程 Thread thread1 = new Thread(group, new MyRunnable(), "Thread1"); Thread thread2 = new Thread(group, new MyRunnable(), "Thread2"); // 设置线程优先级 thread1.setPriority(Thread.MAX_PRIORITY); thread2.setPriority(Thread.MIN_PRIORITY); // 启动线程 thread1.start(); thread2.start(); // 打印线程组中活动线程的数量 System.out.println("Active threads in group: " + group.activeCount()); } } class MyRunnable implements Runnable { public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " is running: " + i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } ``` 在这个示例中,我们创建了一个线程组`MyThreadGroup`,并在该线程组中创建了两个线程`Thread1`和`Thread2`。我们将`Thread1`的优先级设置为最高,将`Thread2`的优先级设置为最低。然后启动这两个线程,并打印线程组中活动线程的数量。 #### 3. 线程同步 在多线程编程中,多个线程可能会同时访问和修改共享资源,这可能会导致数据竞争和不一致的问题。为了避免这些问题,我们需要使用线程同步机制。 在Java中,有两种主要的线程同步机制:同步方法和同步块。同步方法是指在方法声明前加上`synchronized`关键字,这样同一时间只有一个线程可以执行该方法。同步块是指使用`synchronized`关键字包裹一段代码,确保同一时间只有一个线程可以执行该代码块。 以下是一个示例,演示如何使用同步方法来解决数据竞争问题: ```java // Example 1-5 UnsynchBankTest.java public class UnsynchBankTest { public static final int NACCOUNTS = 10; public static final int INITIAL_BALANCE = 10000; public static void main(String[] args) { Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE); int i; for (i = 0; i < NACCOUNTS; i++) { TransferThread t = new TransferThread(b, i, INITIAL_BALANCE); t.setPriority(Thread.NORM_PRIORITY + i % 2); t.start(); } } } /** * A bank with a number of bank accounts. */ class Bank { private int[] accounts; private long ntransacts = 0; public static final int NTEST = 10000; public Bank(int n, int initialBalance) { accounts = new int[n]; int i; for (i = 0; i < accounts.length; i++) { accounts[i] = initialBalance; } ntransacts = 0; } public synchronized void transfer(int from, int to, int amount) throws InterruptedException { while (accounts[from] < amount) { wait(); } accounts[from] -= amount; accounts[to] += amount; ntransacts++; notifyAll(); if (ntransacts % NTEST == 0) { test(); } } public synchronized void test() { int sum = 0; for (int i = 0; i < accounts.length; i++) { sum += accounts[i]; } System.out.println("Transactions:" + ntransacts + " Sum: " + sum); } public int size() { return accounts.length; } } /** * A thread that transfers money from an account to other accounts in a bank. */ class TransferThread extends Thread { private Bank bank; private int fromAccount; private int maxAmount; private static final int REPS = 1000; public TransferThread(Bank b, int from, int max) { bank = b; fromAccount = from; maxAmount = max; } public void run() { try { while (!interrupted()) { for (int i = 0; i < REPS; i++) { int toAccount = (int) (bank.size() * Math.random()); int amount = (int) (maxAmount * Math.random()); bank.transfer(fromAccount, toAccount, amount); sleep(1); } } } catch (InterruptedException e) { } } } ``` 在这个示例中,我们模拟了一个银行系统,有10个账户和10个线程,每个线程负责从一个账户向另一个随机账户转移资金。为了避免数据竞争问题,我们将`transfer`方法和`test`方法声明为同步方法,确保同一时间只有一个线程可以执行这些方法。 #### 4. 死锁问题 死锁是多线程编程中一个常见的问题,它发生在两个或多个线程相互等待对方释放锁的情况下。当发生死锁时,所有线程都无法继续执行,程序会陷入无限等待的状态。 以下是一个简单的死锁示例: ```java public class DeadlockExample { private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(new Runnable() { public void run() { synchronized (lock1) { System.out.println("Thread 1: Holding lock 1..."); try { Thread.sleep(100); } catch (InterruptedException e) { } System.out.println("Thread 1: Waiting for lock 2..."); synchronized (lock2) { System.out.println("Thread 1: Holding lock 1 and lock 2..."); } } } }); Thread thread2 = new Thread(new Runnable() { public void run() { synchronized (lock2) { System.out.println("Thread 2: Holding lock 2..."); try { Thread.sleep(100); } catch (InterruptedException e) { } System.out.println("Thread 2: Waiting for lock 1..."); synchronized (lock1) { System.out.println("Thread 2: Holding lock 2 and lock 1..."); } } } }); thread1.start(); thread2.start(); } } ``` 在这个示例中,`thread1`先获取`lock1`,然后尝试获取`lock2`;而`thread2`先获取`lock2`,然后尝试获取`lock1`。由于两个线程都在等待对方释放锁,因此会发生死锁。 为了避免死锁问题,我们可以采取以下措施: - 避免嵌套锁:尽量避免在一个同步块中获取多个锁。 - 按照相同的顺序获取锁:确保所有线程按照相同的顺序获取锁。 - 使用超时机制:在获取锁时设置超时时间,如果在规定时间内无法获取锁,则放弃。 #### 5. 用户界面编程中的线程 在用户界面编程中,多线程可以提高程序的响应能力。但需要注意的是,Swing不是线程安全的,因此在使用线程与Swing组件交互时,需要遵循一些规则。 以下是一个示例,演示如何在Swing程序中安全地使用线程: ```java // Example 1-7 SwingThreadTest.java import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; /** * This program demonstrates that a thread that runs in parallel with the event dispatch thread can cause errors in Swing components. */ public class SwingThreadTest { public static void main(String[] args) { SwingThreadFrame frame = new SwingThreadFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } /** * This frame has two buttons to fill a combo box from a separate thread. The "Good" button uses the event queue, the "Bad" button modifies the combo box directly. */ class SwingThreadFrame extends JFrame { public static final int WIDTH = 450; public static final int HEIGHT = 300; public SwingThreadFrame() { setTitle("SwingThread"); setSize(WIDTH, HEIGHT); final JComboBox combo = new JComboBox(); JPanel p = new JPanel(); p.add(combo); getContentPane().add(p, BorderLayout.CENTER); JButton b = new JButton("Good"); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { combo.showPopup(); new GoodWorkerThread(combo).start(); } }); p = new JPanel(); p.add(b); b = new JButton("Bad"); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { combo.showPopup(); new BadWorkerThread(combo).start(); } }); p.add(b); getContentPane().add(p, BorderLayout.NORTH); } } /** * This thread modifies a combo box by randomly adding and removing numbers. This can result in errors because the combo box is not synchronized and the event dispatch thread accesses the combo box to repaint it. */ class BadWorkerThread extends Thread { private JComboBox combo; private Random generator; public BadWorkerThread(JComboBox aCombo) { combo = aCombo; generator = new Random(); } public void run() { try { while (!interrupted()) { int i = Math.abs(generator.nextInt()); if (i % 2 == 0) { combo.insertItemAt(new Integer(i), 0); } else if (combo.getItemCount() > 0) { combo.removeItemAt(i % combo.getItemCount()); } sleep(1); } } catch (InterruptedException exception) { } } } /** * This thread modifies a combo box by randomly adding and removing numbers. In order to ensure that the combo box is not corrupted, the editing operations are forwarded to the event dispatch thread. */ class GoodWorkerThread extends Thread { private JComboBox combo; private Random generator; public GoodWorkerThread(JComboBox aCombo) { combo = aCombo; generator = new Random(); } public void run() { try { while (!interrupted()) { EventQueue.invokeLater(new Runnable() { public void run() { int i = Math.abs(generator.nextInt()); if (i % 2 == 0) { combo.insertItemAt(new Integer(i), 0); } else if (combo.getItemCount() > 0) { combo.removeItemAt(i % combo.getItemCount()); } } }); Thread.sleep(1); } } catch (InterruptedException exception) { } } } ``` 在这个示例中,`BadWorkerThread`直接在工作线程中修改`combo`框,可能会导致数据竞争和异常;而`GoodWorkerThread`使用`EventQueue.invokeLater`方法将修改操作转发到事件调度线程中执行,确保了线程安全。 #### 6. 总结 多线程编程是Java中一项强大而复杂的技术,它可以提高程序的性能和响应能力。但同时,多线程编程也带来了一些挑战,如数据竞争、死锁等问题。在实际开发中,我们需要根据具体情况合理使用线程,并遵循一些规则来确保线程安全。 通过本文的介绍,我们了解了Java中多线程编程的基本概念、线程的创建与管理、线程同步、死锁问题以及在用户界面编程中的应用。希望这些知识能够帮助你更好地掌握Java多线程编程。 ### Java多线程编程全面解析 #### 7. 线程的中断与超时处理 在多线程编程中,有时需要中断一个正在运行的线程或者设置操作的超时时间。在Java里,`interrupt`方法可用于请求终止一个线程。当调用线程的`interrupt`方法时,如果线程当前处于阻塞状态(如调用了`sleep`或`wait`方法),会抛出`InterruptedException`异常。 以下是一个示例代码,展示如何安全地停止一个线程: ```java public class MyThread extends Thread { private volatile boolean stopRequested = false; public void run() { try { while (!stopRequested && moreWorkToDo()) { doMoreWork(); } } catch (InterruptedException e) { if (stopRequested) { return; } } } public void requestStop() { stopRequested = true; interrupt(); } private boolean moreWorkToDo() { // 检查是否还有工作要做 return true; } private void doMoreWork() { // 执行具体工作 } } ``` 在这个示例中,`requestStop`方法会设置`stopRequested`标志并调用`interrupt`方法。线程在`run`方法中会不断检查`stopRequested`标志,若该标志为`true`则停止工作。 另外,在进行阻塞调用(如`wait`方法或I/O操作)时,可使用超时机制来限制风险。`wait`方法有带超时参数的重载版本: ```java void wait(long millis) void wait(long millis, int nanos) ``` 这两个方法会等待线程被`notifyAll`或`notify`唤醒,或者等待指定的毫秒数或毫秒与纳秒数。当`wait`方法返回时,可通过计算系统时间差来判断是超时还是被通知唤醒: ```java long before = System.currentTimeMillis(); wait(delay); long after = System.currentTimeMillis(); if (after - before > delay) { // 超时处理 } ``` 对于没有超时设置的I/O操作,可将阻塞操作放在另一个线程中,并使用`join`方法来实现超时: ```java Thread t = new Thread() { public void run() { // 阻塞操作 } }; t.start(); t.join(millis); ``` 若在指定的毫秒数内阻塞操作未完成,`join`方法会返回控制权给当前线程。 #### 8. 线程在动画与定时器中的应用 在Java的小程序(applet)中,线程常用于实现动画效果。动画是通过依次显示一系列图像,给人一种运动的错觉。为了实现动画,可将每个图像帧放在一个单独的文件中,或者将所有帧放在一个文件里。 以下是一个简单的动画小程序示例: ```java import java.awt.*; import java.awt.image.*; import java.awt.event.*; import javax.swing.*; import java.net.*; /** * An applet that shows a rotating globe. */ public class Animation extends JApplet { private Image image; private int current; private int imageCount; private int imageWidth; private int imageHeight; private Thread runner = null; public void init() { addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent evt) { if (runner == null) { start(); } else { stop(); } } }); try { String imageName = getParameter("imagename"); imageCount = 1; String param = getParameter("imagecount"); if (param != null) { imageCount = Integer.parseInt(param); } current = 0; image = null; loadImage(new URL(getDocumentBase(), imageName)); } catch (Exception e) { showStatus("Error: " + e); } } public void loadImage(URL url) throws InterruptedException { image = getImage(url); MediaTracker tracker = new MediaTracker(this); tracker.addImage(image, 0); tracker.waitForID(0); imageWidth = image.getWidth(null); imageHeight = image.getHeight(null); resize(imageWidth, imageHeight / imageCount); } public void paint(Graphics g) { if (image == null) return; g.drawImage(image, 0, - (imageHeight / imageCount) * current, null); } public void start() { runner = new Thread() { public void run() { try { while (!Thread.interrupted()) { repaint(); current = (current + 1) % imageCount; Thread.sleep(200); } } catch (InterruptedException e) { } } }; runner.start(); showStatus("Click to stop"); } public void stop() { runner.interrupt(); runner = null; showStatus("Click to restart"); } } ``` 在这个示例中,`Animation`类继承自`JApplet`。在`start`方法中创建并启动一个线程来控制动画的播放,在`stop`方法中中断线程。 Swing提供了内置的定时器类`Timer`,它使用起来很方便。可通过提供实现`ActionListener`接口的对象和定时器提醒之间的延迟(以毫秒为单位)来构造一个定时器: ```java import javax.swing.Timer; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.GregorianCalendar; import java.util.TimeZone; import javax.swing.JPanel; public class ClockCanvas extends JPanel { private String zone; private GregorianCalendar calendar; public ClockCanvas(String tz) { zone = tz; calendar = new GregorianCalendar(TimeZone.getTimeZone(tz)); Timer t = new Timer(1000, new ActionListener() { public void actionPerformed(ActionEvent event) { calendar.setTime(new Date()); repaint(); } }); t.start(); } // 绘制时钟的方法 public void paintComponent(Graphics g) { super.paintComponent(g); g.drawOval(0, 0, 100, 100); int seconds = calendar.get(GregorianCalendar.HOUR) * 60 * 60 + calendar.get(GregorianCalendar.MINUTE) * 60 + calendar.get(GregorianCalendar.SECOND); double hourAngle = 2 * Math.PI * (seconds - 3 * 60 * 60) / (12 * 60 * 60); double minuteAngle = 2 * Math.PI * (seconds - 15 * 60) / (60 * 60); double secondAngle = 2 * Math.PI * (seconds - 15) / 60; g.drawLine(50, 50, 50 + (int) (30 * Math.cos(hourAngle)), 50 + (int) (30 * Math.sin(hourAngle))); g.drawLine(50, 50, 50 + (int) (40 * Math.cos(minuteAngle)), 50 + (int) (40 * Math.sin(minuteAngle))); g.drawLine(50, 50, 50 + (int) (45 * Math.cos(secondAngle)), 50 + (int) (45 * Math.sin(secondAngle))); g.drawString(zone, 0, 115); } } ``` 在这个示例中,`ClockCanvas`类使用`Timer`每秒更新一次时钟的显示。 #### 9. 进度条与进度监视器的使用 进度条是一个简单的组件,通常是一个部分填充颜色的矩形,用于指示操作的进度。默认情况下,进度会以“n %”的字符串形式显示。 以下是一个使用进度条监控模拟耗时活动的示例代码: ```java import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.Timer; public class ProgressBarTest { public static void main(String[] args) { ProgressBarFrame frame = new ProgressBarFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } class ProgressBarFrame extends JFrame { private Timer activityMonitor; private JButton startButton; private JProgressBar progressBar; private JTextArea textArea; private SimulatedActivity activity; public ProgressBarFrame() { setTitle("ProgressBarTest"); setSize(300, 200); Container contentPane = getContentPane(); textArea = new JTextArea(); JPanel panel = new JPanel(); startButton = new JButton("Start"); progressBar = new JProgressBar(); progressBar.setStringPainted(true); panel.add(startButton); panel.add(progressBar); contentPane.add(new JScrollPane(textArea), BorderLayout.CENTER); contentPane.add(panel, BorderLayout.SOUTH); startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { progressBar.setMaximum(1000); activity = new SimulatedActivity(1000); activity.start(); activityMonitor.start(); startButton.setEnabled(false); } }); activityMonitor = new Timer(500, new ActionListener() { public void actionPerformed(ActionEvent event) { int current = activity.getCurrent(); textArea.append(current + "\n"); progressBar.setValue(current); if (current == activity.getTarget()) { activityMonitor.stop(); startButton.setEnabled(true); } } }); } } class SimulatedActivity extends Thread { private int current; private int target; public SimulatedActivity(int t) { current = 0; target = t; } public int getTarget() { return target; } public int getCurrent() { return current; } public void run() { try { while (current < target && !interrupted()) { sleep(100); current++; } } catch (InterruptedException e) { } } } ``` 在这个示例中,`ProgressBarFrame`类包含一个进度条和一个文本区域。点击“Start”按钮会启动一个模拟活动线程,同时启动一个定时器来监控活动进度并更新进度条。 进度监视器是一个完整的对话框,包含一个进度条、“OK”和“Cancel”按钮。以下是使用进度监视器的示例代码: ```java import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.Timer; public class ProgressMonitorTest { public static void main(String[] args) { JFrame frame = new ProgressMonitorFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } class ProgressMonitorFrame extends JFrame { private Timer activityMonitor; private JButton startButton; private ProgressMonitor progressDialog; private JTextArea textArea; private SimulatedActivity activity; public ProgressMonitorFrame() { setTitle("ProgressMonitorTest"); setSize(300, 200); Container contentPane = getContentPane(); textArea = new JTextArea(); JPanel panel = new JPanel(); startButton = new JButton("Start"); panel.add(startButton); contentPane.add(new JScrollPane(textArea), BorderLayout.CENTER); contentPane.add(panel, BorderLayout.SOUTH); startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { activity = new SimulatedActivity(1000); activity.start(); progressDialog = new ProgressMonitor( ProgressMonitorFrame.this, "Waiting for Simulated Activity", null, 0, activity.getTarget()); activityMonitor.start(); startButton.setEnabled(false); } }); activityMonitor = new Timer(500, new ActionListener() { public void actionPerformed(ActionEvent event) { int current = activity.getCurrent(); textArea.append(current + "\n"); progressDialog.setProgress(current); if (current == activity.getTarget() || progressDialog.isCanceled()) { activityMonitor.stop(); progressDialog.close(); activity.interrupt(); startButton.setEnabled(true); } } }); } } class SimulatedActivity extends Thread { private int current; private int target; public SimulatedActivity(int t) { current = 0; target = t; } public int getTarget() { return target; } public int getCurrent() { return current; } public void run() { try { while (current < target && !interrupted()) { sleep(100); current++; } } catch (InterruptedException e) { } } } ``` 在这个示例中,`ProgressMonitorFrame`类使用`ProgressMonitor`来监控模拟活动的进度。点击“Start”按钮会启动活动和进度监视器,定时器会定期检查活动进度和用户是否点击了“Cancel”按钮。 #### 10. 输入流进度监控 Swing包提供了`ProgressMonitorInputStream`,它是一个有用的流过滤器,能自动弹出一个对话框来监控流的读取进度。 以下是一个使用`ProgressMonitorInputStream`统计文件行数的示例代码: ```java import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.Timer; public class ProgressMonitorInputStreamTest { public static void main(String[] args) { JFrame frame = new TextFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } class TextFrame extends JFrame { private JMenuItem openItem; private JMenuItem exitItem; public TextFrame() { setTitle("ProgressMonitorInputStreamTest"); setSize(300, 200); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu fileMenu = new JMenu("File"); menuBar.add(fileMenu); openItem = new JMenuItem("Open"); openItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { try { openFile(); } catch (IOException exception) { exception.printStackTrace(); } } }); fileMenu.add(openItem); exitItem = new JMenuItem("Exit"); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.exit(0); } }); fileMenu.add(exitItem); } public void openFile() throws IOException { JFileChooser chooser = new JFileChooser(); chooser.setCurrentDirectory(new File(".")); chooser.setFileFilter( new javax.swing.filechooser.FileFilter() { public boolean accept(File f) { String fname = f.getName().toLowerCase(); return fname.endsWith(".txt") || f.isDirectory(); } public String getDescription() { return "Text Files"; } }); int r = chooser.showOpenDialog(this); if (r != JFileChooser.APPROVE_OPTION) return; final File f = chooser.getSelectedFile(); FileInputStream fileIn = new FileInputStream(f); ProgressMonitorInputStream progressIn = new ProgressMonitorInputStream(this, "Reading " + f.getName(), fileIn); InputStreamReader inReader = new InputStreamReader(progressIn); final BufferedReader in = new BufferedReader(inReader); Thread readThread = new Thread() { public void run() { try { final JTextArea textArea = new JTextArea(); String line; while ((line = in.readLine()) != null) { textArea.append(line); textArea.append("\n"); } in.close(); EventQueue.invokeLater(new Runnable() { public void run() { setContentPane(new JScrollPane(textArea)); validate(); } }); } catch (IOException exception) { exception.printStackTrace(); } } }; readThread.start(); } } ``` 在这个示例中,`TextFrame`类通过`JFileChooser`让用户选择文件,使用`ProgressMonitorInputStream`监控文件读取进度,读取完成后将文本显示在`JTextArea`中。 #### 11. 总结与展望 Java多线程编程是一项强大且复杂的技术,能显著提升程序的性能和响应能力。但在实际开发中,需要谨慎处理多线程带来的各种问题,如数据竞争、死锁等。 通过本文的详细介绍,我们深入了解了Java多线程编程的多个方面,包括线程的创建与管理、线程同步、中断与超时处理、在动画、定时器、用户界面编程以及进度监控中的应用等。 在未来的开发中,随着硬件性能的不断提升和软件需求的日益复杂,多线程编程的应用场景会更加广泛。开发者需要不断学习和实践,掌握更多的多线程编程技巧和最佳实践,以应对日益复杂的开发需求。同时,也要关注Java平台的新特性和改进,利用新的工具和方法来简化多线程编程的开发过程,提高代码的可靠性和可维护性。
corwn 最低0.47元/天 解锁专栏
赠100次下载
点击查看下一篇
profit 400次 会员资源下载次数
profit 300万+ 优质博客文章
profit 1000万+ 优质下载资源
profit 1000万+ 优质文库回答
复制全文

相关推荐

李_涛

知名公司架构师
拥有多年在大型科技公司的工作经验,曾在多个大厂担任技术主管和架构师一职。擅长设计和开发高效稳定的后端系统,熟练掌握多种后端开发语言和框架,包括Java、Python、Spring、Django等。精通关系型数据库和NoSQL数据库的设计和优化,能够有效地处理海量数据和复杂查询。
最低0.47元/天 解锁专栏
赠100次下载
百万级 高质量VIP文章无限畅学
千万级 优质资源任意下载
千万级 优质文库回答免费看
立即解锁

专栏目录

最新推荐

机械臂三维模型的材料选择与应用:材质决定命运,选对材料赢未来

![机械臂三维模型的材料选择与应用:材质决定命运,选对材料赢未来](https://blogs.sw.siemens.com/wp-content/uploads/sites/2/2023/12/Inverse-Kinematics-1024x466.png) # 摘要 机械臂作为先进制造和自动化系统的重要组成部分,其三维模型设计和材料选择对提高机械臂性能与降低成本至关重要。本文从基础理论出发,探讨了机械臂三维模型设计的基本原则,以及材料选择对于机械臂功能和耐久性的关键作用。通过对聚合物、金属和复合材料在实际机械臂应用案例的分析,本文阐述了不同材料的特性和应用实例。同时,提出了针对机械臂材料

在线票务系统解析:功能、流程与架构

### 在线票务系统解析:功能、流程与架构 在当今数字化时代,在线票务系统为观众提供了便捷的购票途径。本文将详细解析一个在线票务系统的各项特性,包括系统假设、范围限制、交付计划、用户界面等方面的内容。 #### 系统假设与范围限制 - **系统假设** - **Cookie 接受情况**:互联网用户不强制接受 Cookie,但预计大多数用户会接受。 - **座位类型与价格**:每场演出的座位分为一种或多种类型,如高级预留座。座位类型划分与演出相关,而非个别场次。同一演出同一类型的座位价格相同,但不同场次的价格结构可能不同,例如日场可能比晚场便宜以吸引家庭观众。 -

响应式Spring开发:从错误处理到路由配置

### 响应式Spring开发:从错误处理到路由配置 #### 1. Reactor错误处理方法 在响应式编程中,错误处理是至关重要的。Project Reactor为其响应式类型(Mono<T> 和 Flux<T>)提供了六种错误处理方法,下面为你详细介绍: | 方法 | 描述 | 版本 | | --- | --- | --- | | onErrorReturn(..) | 声明一个默认值,当处理器中抛出异常时发出该值,不影响数据流,异常元素用默认值代替,后续元素正常处理。 | 1. 接收要返回的值作为参数<br>2. 接收要返回的值和应返回默认值的异常类型作为参数<br>3. 接收要返回

【电路设计揭秘】:5个技巧彻底理解电路图的奥秘

![【电路设计揭秘】:5个技巧彻底理解电路图的奥秘](https://electronics.koncon.nl/wp-content/uploads/2020/09/all_components-1-1024x506.jpg) # 摘要 电路图与电路设计是电子工程领域的基石,本文全面概述了电路图的基础知识、核心理论以及设计实践技巧。从电路图基础知识开始,逐步深入到电路设计的核心理论,包括基本电路元件特性、电路理论基础和仿真软件应用。在实践技巧方面,本文介绍了电路图绘制、测试与调试、PCB设计与制造的关键点。进一步探讨了模拟电路与数字电路的区别及应用、电源电路设计优化、微控制器的电路设计应用

【Nokia 5G核心网运维自动化】:提升效率与降低错误率的6大策略

![5g核心网和关键技术和功能介绍-nokia.rar](https://www.viavisolutions.com/sites/default/files/images/diagram-sba.png) # 摘要 随着5G技术的快速发展,其核心网运维面临一系列新的挑战。本文首先概述了5G核心网运维自动化的必要性,然后详细分析了Nokia 5G核心网架构及其运维挑战,包括组件功能、架构演变以及传统运维的局限性。接着,文章探讨了自动化策略的基础理论与技术,包括自动化工具的选择和策略驱动的自动化设计。重点介绍了Nokia 5G核心网运维自动化策略实践,涵盖网络部署、故障诊断与性能优化的自动化实

并发编程:多语言实践与策略选择

### 并发编程:多语言实践与策略选择 #### 1. 文件大小计算的并发实现 在并发计算文件大小的场景中,我们可以采用数据流式方法。具体操作如下: - 创建两个 `DataFlowQueue` 实例,一个用于记录活跃的文件访问,另一个用于接收文件和子目录的大小。 - 创建一个 `DefaultPGroup` 来在线程池中运行任务。 ```plaintext graph LR A[创建 DataFlowQueue 实例] --> B[创建 DefaultPGroup] B --> C[执行 findSize 方法] C --> D[执行 findTotalFileS

AWSLambda冷启动问题全解析

### AWS Lambda 冷启动问题全解析 #### 1. 冷启动概述 在 AWS Lambda 中,冷启动是指函数实例首次创建时所经历的一系列初始化步骤。一旦函数实例创建完成,在其生命周期内不会再次经历冷启动。如果在代码中添加构造函数或静态初始化器,它们仅会在函数冷启动时被调用。可以在处理程序类的构造函数中添加显式日志,以便在函数日志中查看冷启动的发生情况。此外,还可以使用 X-Ray 和一些第三方 Lambda 监控工具来识别冷启动。 #### 2. 冷启动的影响 冷启动通常会导致事件处理出现延迟峰值,这也是人们关注冷启动的主要原因。一般情况下,小型 Lambda 函数的端到端延迟

ApacheThrift在脚本语言中的应用

### Apache Thrift在脚本语言中的应用 #### 1. Apache Thrift与PHP 在使用Apache Thrift和PHP时,首先要构建I/O栈。以下是构建I/O栈并调用服务的基本步骤: 1. 将传输缓冲区包装在二进制协议中,然后传递给服务客户端的构造函数。 2. 构建好I/O栈后,打开套接字连接,调用服务,最后关闭连接。 示例代码中的异常捕获块仅捕获Apache Thrift异常,并将其显示在Web服务器的错误日志中。 PHP错误通常在Web服务器的上下文中在服务器端表现出来。调试PHP程序的基本方法是检查Web服务器的错误日志。在Ubuntu 16.04系统中

Clojure多方法:定义、应用与使用场景

### Clojure 多方法:定义、应用与使用场景 #### 1. 定义多方法 在 Clojure 中,定义多方法可以使用 `defmulti` 函数,其基本语法如下: ```clojure (defmulti name dispatch-fn) ``` 其中,`name` 是新多方法的名称,Clojure 会将 `dispatch-fn` 应用于方法参数,以选择多方法的特定实现。 以 `my-print` 为例,它接受一个参数,即要打印的内容,我们希望根据该参数的类型选择特定的实现。因此,`dispatch-fn` 需要是一个接受一个参数并返回该参数类型的函数。Clojure 内置的

编程中的数组应用与实践

### 编程中的数组应用与实践 在编程领域,数组是一种非常重要的数据结构,它可以帮助我们高效地存储和处理大量数据。本文将通过几个具体的示例,详细介绍数组在编程中的应用,包括图形绘制、随机数填充以及用户输入处理等方面。 #### 1. 绘制数组图形 首先,我们来创建一个程序,用于绘制存储在 `temperatures` 数组中的值的图形。具体操作步骤如下: 1. **创建新程序**:选择 `File > New` 开始一个新程序,并将其保存为 `GraphTemps`。 2. **定义数组和画布大小**:定义一个 `temperatures` 数组,并设置画布大小为 250 像素×250 像