一、进程(process)和线程(thread)
1、概念
进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础。他的狭义定义是:进程是正在运行的程序的实例,广义的定义是进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,他是操作系统动态执行的基本单元。
线程是程序执行时的最小单元,线程是进程中的一个实体,是被系统独立调度和分派的基本单位。同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,线程在运行中也会呈现出间断性,也有就绪、阻塞和运行三种基本状态。
2、进程的特征
进程主要有四个特征:动态性、并发性、独立性和异步性。
动态性:进程的是指是程序在系统中的一次执行过程,进程是动态产生动态消亡的;
并发性:任何进程都可以同其他的进程一起并发执行;
独立性:进程是一个能独立运行的基本单位,也是系统分配资源和调度的独立单位;
异步性:进程间的相互制约,使得进程具有执行的间断性,进程间各自独立的向前推进。
3、线程的特征
在多线程系统中,通常是一个进程包括多个线程,每个线程都是作为cpu的基本单位。
(1)轻型实体:线程中的实体基本上不拥有系统资源,只拥有少量的能保证独立运行的资源;
(2)独立调度和分配的基本单元:在多线程系统中,线程是能独立运行的基本单位,也是独立调度和分配的最小单位,因此线程的切换非常的迅速并且开销小(同一进程中);
(3)可并发执行:在一个进程中的多个线程之间,可以并发执行,同样不同的进程中的线程也可以并发执行,充分的利用和发挥了处理机与外围设备并行工作的能力;
(4)共享进程资源:在同一进程中的各个线程,都可以共享该进程所拥有的资源。
4、进程的状态
进程的执行间断性,决定了进程可能具有多种状态,他主要有以下三种状态:
(1)运行状态:进程占用处理器资源,处于这个状态的进程的数目小于等于处理器的数目,在没有其他的进程可以执行时,通常会自动执行系统的空闲进程。
(2)就绪状态:进程已经获得除了处理器之外的所需资源,等待分配处理器资源,只要分配了就可以执行。就绪的进程按照优先级来划分队列。
(3)阻塞状态:进程由于等待某个条件而无法继续执行,在该状态中就算分配了处理器资源,该进程也无法运行。
5、进程、线程、多线程的比较
1、进程是资源分配的基本单位;
2、线程是CPU独立圆形和调度的基本单位;
3、进程具有独立的空间地址,一个进程崩溃后,在保护模式下不会对其他的进程产生影响;
4、线程只是一个进程的不同执行路径,线程由自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉等于整个进程死掉。
二、Java中线程的创建
Java中创建线程的方式主要有三种:(1)实现Runnable接口,(2)继承Thread类,(3)使用Callable和Future接口创建。
1、实现Runnable接口
在Java中使用实现Runnable接口的方法创建线程时,必须重写其run方法,在java.lang.Runnable接口中也只有一个接口run。
通过实现Runnable接口,定义了一个子任务,然后将子任务交由Thread去执行,在这种方式下必须将Runnable作为Thread的参数,通过Thread的start方法来创建一个新的线程来执行该子任务。如果是直接调用Runnable的run方法的话,是不会创建新的线程的,就跟普通的方法调用没有任何区别。具体的可以实现下面的例子来看看最终的结果区别。
public class Test1 {
public static void main(String[] args) {
System.out.println("主线程Id:"+Thread.currentThread().getId());
//通过Thread调用子任务
Thread thread = new Thread(new MyRunnable());
thread.start();
//直接调用run方法,不会创建新的线程
new MyRunnable().run();
}
}
class MyRunnable implements Runnable{
public MyRunnable() {
}
@Override
public void run() {
System.out.println("线程Id:"+Thread.currentThread().getId());
}
}
2、继承Thread类
继承Thread类,必须重写run方法,在run中定义需要执行的任务。
同样在创建好了线程类之后,就可以创建线程对象,然后通过start()方法去启动线程,同样要注意的是不是调用run()方法去启动线程,run()方法中只定义需要执行的任务,如果直接调用run方法,相当于普通的方法调用。
根据下面的例子可以看出:
thread2和thread1的线程Id不同,thread2与主线程Id相同,说明通过run方法调用不会创建新的线程,而是直接在主线程中直接运行run方法;
thread1的start方法在thread2的run方法前面调用,但是结果却是thread2的run方法先执行,说明新线程的创建过程不会阻塞主线程的后续执行。
public class Test1{
public static void main(String[] args) {
System.out.println("主线程Id:"+Thread.currentThread().getId());
//通过Thread调用子任务
//Thread thread = new Thread(new MyRunnable());
//thread.start();
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.run();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("子线程:threadName="+name+" id="+Thread.currentThread().getId());
}
}
start()方法调用后并不是立即执行多线程代码,而是使得线程变为可运行状态,具体什么时候执行是由操作系统决定的。
3、使用Callable和Future接口创建
使用Callable接口来创建线程是创建Callable接口的实现类,并实现call()方法,然后使用FutureTask类来包装Callable实现类的对象,最后以FutureTask对象作为Thread的对象来创建线程。
C
public static void main(String[] args) {
System.out.println("主线程Id:"+Thread.currentThread().getId());
//创建MyCallable对象
Callable<Integer> mycallable = new MyCallable();
//使用FutureTask来包装MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(mycallable);
Thread thread = new Thread(ft);
thread.start();
try {
int sum = ft.get();
System.out.println(sum);
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = 0;
// 与run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
int sum = 0;
for (i=0; i < 100; i++) {
sum += i;
}
System.out.println(Thread.currentThread().getName() + " " + i);
return sum;
}
}
在上面的例子中,可以看到实现Callable接口中不再是run()方法,而是call()方法,同时与前两种方法区别是执行体中可以有返回值。
在创建新的线程时,是通过FutureTask来包装MyCallable对象,下面是FutureTask类的定义:
//FutureTask类的定义
public class FutureTask<V> implements RunnableFuture<V> {
}
//RunnableFuture接口的定义
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可以看出FutureTask实际上是实现了Runnable和Future接口,因此具有Future和Runnable的特性,根据Runnable可以作为Thread对象的target,而Future使它可以取得新创建的线程中的call()方法的返回值。
4.三种方法创建线程的比较
(1)实现Runnable接口和Callable接口适合多个相同的程序代码的线程去处理同一个资源;
(2)实现Runnable接口和Callable接口与继承Thread类相比可以避免java中的单继承限制;
(3)实现Runnable接口和Callable接口与集成Thread类相比,的编程稍微复杂,如果需要访问当前线程,则需要使用Thread.currentThread(),而Thread类中直接使用this;
(4)在一般使用中比较常用的是实现Runnable和Callable接口的方式创建多线程。