每一个任务称为一个线程,同时可以运行一个以上的程序称为多线程程序。
多线程与多进程本质的区别在于每个进程拥有自己的一套变量,而线程则共享数据。与进程相比,线程更轻量级,创建撤销一个线程比启动新进程容易的多。
以下程序中,当点击start后有一个小球开始在弹跳,但这个程序是一个单线程的,当球跳动时,点击close,程序不会结束,因为线程被球弹跳所占用,无法马上响应close。
Ball.java记录了运动的小球的坐标(x,y),并且有move方法将小球移动到下一个位置。
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import javax.swing.JPanel;
public class BallComponent extends JPanel {
private ArrayList<Ball> balls=new ArrayList<Ball>();
/*
* add a new ball to balls
*/
public void add(Ball b){
balls.add(b);
}
/*
* paint the panel
*/
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2=(Graphics2D)g;
g2.setPaint(Color.RED);
for(Ball b:balls){
g2.fill(b.getShape());
}
}
}
BallComponent.java继承了JPanel,用来画图。
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
public class Ball {
private static final int XSIZE=15;
private static final int YSIZE=15;
private double x=0;
private double y=0;
private double dx=1;
private double dy=1;
/*
* change the value of x and y of the next position of ball
*/
public void move(Rectangle2D bounds){
x+=dx;
y+=dy;
if(x<bounds.getMinX()){
x=bounds.getMinX();
dx=-dx;
}
if(x+XSIZE>=bounds.getMaxX()){
x=bounds.getMaxX()-XSIZE;
dx=-dx;
}
if(y<bounds.getMinY()){
y=bounds.getMinY();
dy=-dy;
}
if(y+YSIZE>=bounds.getMaxY()){
y=bounds.getMaxY()-YSIZE;
dy=-dy;
}
}
/*
* the shape of the ball at current position
*/
public Ellipse2D getShape(){
return new Ellipse2D.Double(x,y,XSIZE,YSIZE);
}
}
BounceFrame.java完成整个界面,点击start按钮时调用addBall方法。
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BounceFrame extends JFrame {
private static final int DEFAULT_WIDTH=450;
private static final int DEFAULT_HEIGHT=350;
private static final int STEPS=10000;
private static final int DELAY=3;
private BallComponent comp;
public BounceFrame(){
setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
comp = new BallComponent();
add(comp,BorderLayout.CENTER );
JPanel buttonPanel=new JPanel();
addButton(buttonPanel,"Start",new ActionListener(){
public void actionPerformed(ActionEvent e) {
addBall();
}
});
addButton(buttonPanel,"Close",new ActionListener(){
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
add(buttonPanel,BorderLayout.SOUTH );
}
/*
* add button and listener to Container
*/
private void addButton(JPanel c, String title,
ActionListener listener) {
JButton button=new JButton(title);
c.add(button);
button.addActionListener(listener);
}
private void addBall() {
try{
Ball ball=new Ball();
comp.add(ball);
for(int i=0;i<STEPS;i++){
ball.move(comp.getBounds());
comp.paint(comp.getGraphics());//如果调用repaint,不会重画面板,因为addBall完全掌握这控制权
Thread.sleep(DELAY);
}
}catch(InterruptedException e){
}
}
}
然后是程序的入口:
import java.awt.EventQueue;
import javax.swing.JFrame;
public class Bounce {
public static void main(String[] arg){
EventQueue.invokeLater(new Runnable(){
public void run(){
JFrame frame=new BounceFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
要把这个程序改为多线程,要做如下改变:
如果将球移动的代码放在一个独立的线程中,这样每个球都在自己的线程中运行。由于每个线程都有机会得到运行,所以在在球弹跳期间用户点击close时,事件调度线程有机会关注到这一动作。
要想把弹跳球代码放在一个独立的线程中,只要实现Runnable接口,将动画代码放在run中。代码如下:
BallRunnable implements Runnable{
... ...
public void run() {
try{
for(int i=0;i<STEPS;i++){
ball.move(comp.getBounds());
comp.repaint();
Thread.sleep(DELAY);
}
}catch(InterruptedException e){
}
}
... ...
}
当点击start后启动一个新线程:
Ball ball=new Ball();
comp.add(ball);
Runnable r=new BallRunnable(ball,comp);
Thread t=new Thread(r);
t.start();
也可以继承Thread类来定义一个线程,然后再点击start后构造一个子类对象,启动线程。这种方法已经不再推荐,要为每一个任务创建独立的线程付出的代价太大。
import java.awt.Component;
public class BallRunnable implements Runnable{
private Ball ball;
private Component comp;
private static final int STEPS=100000;
private static final int DELAY=3;
public BallRunnable(Ball ball,Component comp){
this.ball=ball;
this.comp=comp;
}
public void run() {
try{
for(int i=0;i<STEPS;i++){
ball.move(comp.getBounds());
comp.repaint();
Thread.sleep(DELAY);
}
}catch(InterruptedException e){
}
}
}
改进后的BounceFrame.java
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BounceFrame extends JFrame{
private static final int DEFAULT_WIDTH=450;
private static final int DEFAULT_HEIGHT=350;
private BallComponent comp;
public BounceFrame(){
setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
comp = new BallComponent();
add(comp,BorderLayout.CENTER );
JPanel buttonPanel=new JPanel();
addButton(buttonPanel,"Start",new ActionListener(){
public void actionPerformed(ActionEvent e) {
addBall();
}
});
addButton(buttonPanel,"Close",new ActionListener(){
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
add(buttonPanel,BorderLayout.SOUTH );
}
/*
* add button and listener to Container
*/
private void addButton(JPanel c, String title,
ActionListener listener) {
JButton button=new JButton(title);
c.add(button);
button.addActionListener(listener);
}
private void addBall() {
Ball ball=new Ball();
comp.add(ball);
Runnable r=new BallRunnable(ball,comp);
Thread t=new Thread(r);
t.start();
}
}