导言
我们在日常使用计算机的时候有这样的习惯:可能我们在运行一个音乐播放器软件的同时我们还在使用一个聊天软件,还或许会顺便浏览网页信息。你想过没有,它们三个都是彼此独立的应用软件,但是你基本上可以在同一时间同时使用它们。但是我们学过计算机在处理程序的时候都是按步骤来的,假如目前计算机的CPU(Central Processing Unit)只有一个物理核心,那么意味着极限趋于0的时间内它只能进行一个程序的计算。那么我们为什么感觉三个程序在同时运行呢?为了解决这个问题我们就需要用到线程和进程的概念。
从计组引入进程
计算机中的程序处理和计算都用到了最大的核心:CPU(Central Processing Unit),CPU是一个计算机中最为核心的东西,相当于人的大脑。CPU的复杂程度相当大,在我们计算机专业的学习中我们专门会有一本书叫做《计算机组成原理》来描述CPU的具体工作方式。其中就会提到线程和进程以及多任务等等的CPU层面的实现方式,另外我们如果要对多线程进行完整详细的了解我们还需要学习例如《操作系统》等系统层面的知识。但是我们在编程中其实不用那么详细的了解线程与进程的实现。
![]()
Windows中的进程
进程和线程的区别
首先看下定义:
进程定义:进程是正在运行的程序的实例。
线程定义:线程有时被称为轻量级进程,是程序执行流的最小单元。
我们可以看出进程的范围是大于线程的。确实是,一个程序是由一个或者多个进程完成的,而一个进程又是有一个或者多个线程构成的。线程是不拥有系统资源的,它只拥有一点运行过程中必不可少的资源。但是进程是和其他进程共享系统其他资源的。所以总的来说线程是被进程所包含的,是将进程拆分成一个个顺序执行流的最小单元的。
![]()
进程和线程的关系
创建线程的两种方法
在Java中线程是被定义好的类,关键字是Thread,既然是个类,这里我们就来看第一种实现它的办法:继承。
我们查阅参考文档中的Thread类的方法来看到我们将使用的方法:
![]()
然后先上代码:
【Thread】
public class ThreadTest {
public static void main(String[] args) {
ThreadofDemo tod = new ThreadofDemo();
tod.start();//result:This is a thread test.
}
}
class ThreadofDemo extends Thread{//通过继承一个线程类来开辟一个线程
@Override
public void run() {//重写父类中的run方法来创建一个线程
super.run();
System.out.println("This is a thread test.");
}
}
为什么main方法中调用的不是run而是start呢?这里看我们上边的官方文档解释就可以看出start方法实际上就是调用的run方法的方法。我们通过继承来重写父类里的run方法实现对线程类的操作。
这里问题就来了?假如我们现在的类已经是一个子类,那么按照Java的单继承规范,我们是不能继承线程类了,那么该怎么办呢?首先不查文档我们可以先想想,为了弥补多继承Java中还存在一个特殊的东西:接口(Interface)。那么我们该实现哪个接口呢?我们打开官方文档再次进行查询:
![]()
这里赫然写着接口Runnable的一个实现类就是Thread,所以其实我们继承的Thread它也是一个Runnable的实现类,既然是这样,能够多接口实现的Java类何不能直接去实现这个接口呢?那么解决办法马上就来了:
【通过实现Runnable接口来创建进程】
public class ThreadTest {
public static void main(String[] args) {
ThreadofDemo tod = new ThreadofDemo();
Thread th = new Thread(pm);//使用线程对象来接受实现类
th.start();
}
}
class ThreadofDemo implements Runnable{//实现接口
@Override
public void run() {//重写方法
System.out.println("This is a thread test.");
}
}
我们可以看出线程对象可以接收一个实现Runnable接口的实现类来创建线程。我们在实现类中也重写了run方法,这也证明了run方法是被定义在Runnable接口中的方法,不是Thread类的特有方法。
现在我们已经可以用是两种方法创建线程了。那么线程到底在这里起到了什么作用?
线程的并发性
我们前面讨论了计算机中我们可以同时去使用多个软件,即同时运行多个程序。那么计算机内部它们真的是同时运行的么?如果你有相关的操作系统和计算机组成方面的知识不难得出在计算机中其实一个物理核心在时间线上是只能运行一段程序的,但是系统将每个程序划分为进程,每个进程有划分为线程。然后系统在人能感知到的时间最小单位以外的时间内先后运行了这些进程和线程。这些进程和线程运行切换的时间一般是毫秒级或以下的,所以人感觉它们是在同时运行的,这就是线程或者进程的并发性。
下面模拟两个线程同时进行下载来看看线程并发性的特点:
首先我们使用创建线程的第一种方式来实现它:
public class ThreadTest {
public static void main(String[] args) {
ThreadofDemo tod1 = new ThreadofDemo();
tod1.setName("Thread 1:");
ThreadofDemo tod2 = new ThreadofDemo();
tod2.setName("Thread 2:");
tod1.start();
tod2.start();
}
}
class ThreadofDemo extends Thread{
@Override
public void run() {
super.run();
//System.out.println("This is a thread test.");
for(int i=0; i<=100; i++){
System.out.println(ThreadofDemo.currentThread().getName()+"下载已经完成:"+i+"%");
}
System.out.println(ThreadofDemo.currentThread().getName()+"下载完成");
}
}
使用第二种实现接口的创建方法来实现:
public class ThreadTest {
public static void main(String[] args) {
PrintMessage pm1 = new PrintMessage(1);
Thread th1 = new Thread(pm1);
PrintMessage pm2 = new PrintMessage(2);
Thread th2 = new Thread(pm2);
th1.start();
th2.start();
}
}
class PrintMessage implements Runnable{
int num = 0;
PrintMessage(int num){
this.num = num;
}
@Override
public void run() {
for(int i=0; i<=100; i++){
System.out.println("第"+num+"个下载已经完成:"+i+"%");
}
System.out.println("第"+num+"下载完成");
}
}
运行结果:
从运行结果分析,两个独立的线程之间在运行时不是先后分明的,它们之间往往是交叉运行的,这很好的验证了我们之前说的并发性,其实在它上面的进程也是类似的并发性运行。这里其实整个IDE在系统中就是一个进程,它下面包含了许许多多的线程,其中我们的实例就是两个线程,这写线程的并发运行构成了多线程。
书签:
这里细心的人会发现了setName和getName的方法,由于篇幅的限制,我特意将这部分的讨论拆分成独立的小博文,关于更多的线程和进程的讨论请参考后续的“关于Java中线程和进程的讨论2”。