多线程编程,相信大家或多或少都有听到过。以前对这个概念的理解比较模糊,最近写了一个简单的JAVA程序来帮助自己理解这个概念。
一、什么是程序、进程和线程?
- 程序:程序是一个指令序列。执行某一个进程所需要的所有文件,这些文件最后都会被翻译成指令序列。
- 进程:进程是正在运行的程序的实例。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。
- 线程:线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
二、这三者之间的区别
程序和进程之间的区别
- 进程是动态的,而程序是静态的。
- 进程有一定的生命期,而程序是指令的集合,不存在生命期。
- 一个程序可以对应多个进程,但一个进程只能对应一个程序。
我们可以把程序看做剧本,进程看做演出。也就是一个剧本可以同时进行多场演出,但是每场演出只有一个剧本。
进程和线程的区别
- 地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
- 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信。
- 调度和切换:线程上下文切换比进程上下文切换要快得多。
- 在多线程OS中,线程不是一个可执行的实体。
三、为何需要多线程编程
一个主要的原因就是为了充分利用CPU资源,提高运行速率。线程在CPU里面是并发执行的。如果第一个线程执行到了一些需要长时间等待的操作时,PC并不会一直等待这个操作结束,而是控制转移给另一个线程,去执行它。等到第一个线程的等待时间结束后再重新调回第一个线程,执行它。这样子我们就可以大大减少CPU的闲置时间。
四、JAVA实现线程的方式
- Runnable 接口:里面带有run方法,也就是线程的执行方法
- Thread 类,该类实现Runnable接口:A.run方法;B.start线程的启动方法。这两个是最重要的方法,其他的还有一些比如sleep等等。
线程的启动必须要靠start方法,如果你用的是Runnable接口,那么你必须额外定义一个Thread类对象,利用它的start方法来启动线程。如果你用的是Thread类,那么直接调用对象的start方法即可。
五、程序实例——小球运动
在界面实现多个自由运动的小球。单线程编程的话只能实现一个运动的小球,因为每一个运动的小球都是不会停止的,它会一直占用一个线程。
代码部分如下:
//实现球运动的界面类BallFrame
import javax.swing.JFrame;
public class BallFrame extends JFrame{
static public void main(String[] args) {
BallFrame jf=new BallFrame();
jf.initUI();
}
public void initUI() {
this.setTitle("小球运动");
this.setSize(800, 600);
this.setDefaultCloseOperation(3);
this.setLocationRelativeTo(null);
this.setResizable(false);
this.setVisible(true);
//事件监听机制的画笔必须等到画板画完了才能取
frameListener fl=new frameListener(this);
this.addMouseListener(fl);
}
}
//对界面设置监听机制
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
public class frameListener implements MouseListener{
public BallFrame bf;
public int x;
public int y;
public frameListener(BallFrame bf) {
this.bf=bf;
}
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
x=e.getX();
y=e.getY();
Ball ball=new Ball(x,y,Color.blue,bf);
//如果直接调用了run,那么程序不会新建一个线程,而只会在当前线程中运行
//这使得run只会被当做一个普通方法
//ball.run();
//继承Thread,启动球类对象的线程
//ball.start();
//实现Runable接口,需要把实现的接口类对象传给Thread,再由Thread去调用start方法
Thread t=new Thread(ball);
t.start();
}
@Override
public void mouseEntered(MouseEvent arg0) {
}
@Override
public void mouseExited(MouseEvent arg0) {
}
@Override
public void mousePressed(MouseEvent arg0) {
}
@Override
public void mouseReleased(MouseEvent arg0) {
}
}
//实现一个球类,继承Thread类,或者实现Runnable接口
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
//public class Ball extends Thread{
public class Ball implements Runnable{
public int x;
public int y;
public BallFrame bf;
public Graphics g;
public Color color;
public double speedx=0;
public double speedy=0;
public Ball(int x,int y,Color color,BallFrame bf) {
this.x=x;
this.y=y;
this.color=color;
this.bf=bf;
this.g=bf.getGraphics();
}
public void Speed() {
//随机获取小球的移动方向
//先随机获取小球xy的移动速度
int dadii=5;//半径长
Random random=new Random();
speedx=random.nextInt(6);
double computer=dadii*dadii-speedx*speedx;
speedy=Math.sqrt(computer);
//随机把小球xy移动速度的值设置为正负
int choosex=random.nextInt(2);
if(choosex==0) speedx*=-1;
int choosey=random.nextInt(2);
if(choosey==0) speedy*=-1;
//判断当前位置是否越界
if(y>bf.getHeight()) speedy*=-1;
else if(y<0) speedy*=1;
if(x>bf.getWidth()) speedx*=-1;
else if(x<0) speedx*=1;
}
//重写run方法
public void run() {
while(true) {
g.setColor(color);
g.drawOval(x, y, 20, 20);
Speed();
y+=speedy;
x+=speedx;
try {
//sleep函数存在Thread中
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
六、总结
1.直接继承Thread类会比实现Runnable接口简洁一点,如果实现Runnable接口你还是要额外去实例化一个Thread类的对象。
2.线程之间不会互相干扰。我们这个程序每一个小球的运动都是不会结束,它们会一直在运行。当你每次点击界面时,进程都会帮你创建一个线程,去实现一个运动的小球。这些小球之间互不干扰。但是如果你把run方法当做一个普通方法来调用时,只有第一次点击界面时会生成一个运动的小球,接下去无论你怎么点击,界面都没有反应。因为你是在尝试往同一个线程里面生成多个小球,但是由于当前这个小球还没有运行结束,当前线程一直有指令需要执行,因此无法对你进行的鼠标点击操作做出相应的反应。