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平台的新特性和改进,利用新的工具和方法来简化多线程编程的开发过程,提高代码的可靠性和可维护性。
0
0
复制全文
相关推荐









