进程:从狭义的角度来讲,进程就是一个正在运行的程序的实例。
线程:指的是进程中单一顺序的执行流,有时候也叫作轻量级进程。
举个例子:
当我们用word 2016程序 编辑word的时候,word2016 可以实时检查拼写的错误。 我们先运行word 2016 程序,
打开任务管理器,切换到进程页面,就可以找到word2016的进程,这就是正在运行的word2016 的一个实例
同时也可以看到,一个运行的中的程序可能不止有一个进程,可能有多个进程,每个进程都执行不同的任务,如图中的QQ音乐和电脑管家。
我们继续以word2016 来举例说明线程。可以看到在运行的过程中,word只有一个进程,但是却可以同时执行不同的功能。
这是因为word2016 的进程中,有多个线程,如 有一个线程在接收键盘输入的字并以适当的格式显示在屏幕上,而另一个线程在“读取”我们输入的字符或者文本,然后分析并标记错误的部分。
这就和我们以前运行java程序的时候形成对比了。以前我们运行java程序的时候,是从mian函数从上往下按顺序执行的,当调用函数的时候,也要等调用的函数执行完了,才能继续往下执行。而,如果在我们的程序里加入多线程,就可以不用等函数调用完成,可以继续往下执行下面的语句。
线程从产生到消亡,一共有五个状态
1. Newborn 状态:线程被创建但没有开始执行的初始状态。直到调用Thread类的run()方法后,线程会进入Runable状态
2.Runable状态:线程处在准备就绪的状态,随时可被调用执行。(注意:对线程调用run()方法后,不代表线程立即执行,Runable状态的线程会进入一个等待队列中等待执行)
3. Running 状态: 线程正在运行的状态,表示线程已经拥有处理器的控制权。正常情况下,当run()方法运行结束后,该线程就进入Dead状态。
4. Blocked状态: 线程的堵塞状态。处于阻塞状态的的线程必须由某些事件唤醒,至于是何种事件,则取决于阻塞发生的原因。
5. Dead 状态: 死亡或终止状态,表示线程已经退出运行的状态。进入这一状态的线程有多方面的原因,可能是线程执行完毕,正常终止也可能是一个线程被另一个线程强行中断。
为了便于理解和记忆,笔者特地画了一个流程图:
步骤:
1.创建一个类,并继承(extends)Thread类
2. 重载父类Thread的run()方法(即把你想要在这个线程中执行的代码放入run()方法里)
3. 实例化(new)该线程的一个对象
4, 调用对象的start()方法启动线程
ps:线程启动后自动执行run()方法里的内容,执行完毕后,线程进入终止的状态,
下面是一个简单的实例:
//多线程的实现方法之一: 通过继承Thread类实现
public class myThread00 extends Thread {
public void run(){ //需要重写run方法
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+" - running");}
} //Thread.currentThread().getName() 会返回正在执行的该方法的线程的名字,该名字一般由java虚拟机自己生成
public static void main(String[]args){
myThread00 mt0=new myThread00();
myThread00 mt1= new myThread00();
mt0.start();// 调用start方法而不是run方法啊
mt1.start();
for(int i=0;i<5;i++){
System.out.println("for: "+Thread.currentThread().getName()+" - running");}
}
}
运行结果如下图:(运行结果不唯一)
(ps: Thread-0 ,Thread-1 ,main 代表三个不同的线程)
读者多运行几次也会得到不同的结果,上面右边的运行结果是比较明显的,可以看出 两个线程是在同时运行的。表现出来的结果就是交替打印(毕竟计算机每秒运算上百万次,所以要想看出更明显的效果,可以把循环的次数适当增大。)
需要说明的是,我们在main方法里新建并启动了一个线程,但实际上,还有一个线程在运行main方法,我们这个线程叫做主线程。主线程负责新建和启动其他线程。Thread.currentThread().getName()返回的名字为main即代表该线程是主线程。如在这个例子中, 通过主线程新建并启动了两个子线程。
通过这个简单的例子,我们能得到下面几个结论:
1. 创建独立执行的线程还是比较容易的(Java JVM负责处理了大部分细节)
2. 无法准确的知道线程在什么时间开始执行的(因为这是操作系统决定的)
3. 线程间的执行是相互独立的
4. 线程独立于启动它的线程。
我们知道,java类是单继承的,每个类只能有一个父类,所以如果我们通过继承Thread类的方法来构造线程,这个类就不能继承其他需要的类,这可能会对我们的编程造成很大的限制。所以实现多线程一般通过实现Runable接口实现,从而克服了单一继承方式所造成的限制。
先来看下Runable接口的定义:
public interface Runable{
public abstract void run();
}
看起来和一般的接口没有什么不一样的,它是怎么实现多线程的呢?答案是 和一个Thread类对象绑定 。
我们将前面的例子稍微改一下,改成通过实现Runable接口的方式构造线程
//多线程的实现方法之二: 实现Runnable接口。和继承自Thread类差不多,不过实现Runnable后,还是要通过一个Thread来启动:
public class myThread01 implements Runnable{
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+" - running");
}
}
public static void main(String[] args){
myThread01 mt1 = new myThread01();//实例化类
myThread01 mt2 = new myThread01();
Thread t1 = new Thread(mt1); //绑定Thread类对象,并通过Thread类启动
Thread t2 = new Thread(mt2);
//Thread t1 = new Thread(new myThread01());
//Thread t2 = new Thread(new myThread01()); 这两行和上面四行效果是一样的,是一种简便的写法
t1.start();//还是通过start方法启动
t2.start();
for (int i=0;i<5;i++){
System.out.println("for: "+Thread.currentThread().getName()+" - running");
}
}
运行效果如下(效果不唯一):这是比较明显的结果。。毕竟打印5句话实在是太快了,想看到明显效果的话,可以把循环次数改为500000...
ps:lambda表达式是java8加入的特性,如果想要的了解话,可以看看我写的这篇博客
/**
* 多线程的实现方法之三:
* //通过lambda表达式方法创建,本质就是实现Runable接口
*
* @author manzuo
*/
public class myThread01 {
public static void main(String[] args) {
//通过lambda表达式方法创建,本质就是实现Runable接口
Thread t1 = new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - running");
}
});
//还是通过start方法启动
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println("for: " + Thread.currentThread().getName() + " - running");
}
}