文章要配合源码看
进程和线程
进程的查看可以通过任务管理器查看
什么是进程
简单来说,进程是程序的一次启动执行。 程序是存放在硬盘中的可执行文件,主要包括代码指令和数据。 一个进程是一个程序的一次启动和执行,是操作系统将程序装入内存,给程序分配必要的系统资源,并且开始运行程序的指令。
进程的基本原理
进程的大致结构:
程序段 一般也被称为代码段。代码段是进程的程序指令在内存中的位置,包含需要执行的指令集合;
数据段 是进程的操作数据在内存中的位置,包含需要操作的数据集合;
程序控制块 (ProgramControl Block,PCB)包含进程的描述信息和控制信息,是进程存在的唯一标志。
PCB主要由四大部分组成:
进程的描述信息。主要包括:进程ID和进程名称,进程ID是唯一的,代表进程的身份;进程状态,比如运行、就绪、阻塞;进程优先级,是进程调度的重要依据。
进程的调度信息。主要包括:程序起始地址,程序的第一行指令的内存地址,从这里开始程序的执行;通信信息,进程间通信时的消息队列。
进程的资源信息。主要包括:内存信息,内存占用情况和内存管理所用的数据结构;I/O设备信息,所用的I/O设备编号及相应的数据结构;文件句柄,所打开文件的信息。
进程上下文。主要包括执行时各种CPU寄存器的值、当前程序计数器(PC)的值以及各种栈的值等,即进程的环境。在操作系统切换进程时,当前进程被迫让出CPU,当前进程的上下文就保存在PCB结构中,供下次恢复运行时使用。
Java编写的程序都运行在Java虚拟机(JVM)中,每当使用Java命令启动一个Java应用程序时,就会启动一个JVM进程。在这个JVM进程内部,所有Java程序代码都是以线程来运行的。JVM找到程序的入口点main()方法,然后运行main()方法,这样就产生了一个线程,这个线程被称为主线程。当main()方法结束后,主线程运行完成,JVM进程也随即退出。
线程的基本原理
线程是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小单位。
线程是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小单位。
线程是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小单位。 在线程的结构中,线程描述信息即线程的基本信息,主要包括: (1)线程ID(Thread ID,线程标识符)。线程的唯一标识,同一个进程内不同线程的ID不会重叠。 (2)线程名称。主要是方便用户识别,用户可以指定线程的名字,如果没有指定,系统就会自动分配一个名称。 (3)线程优先级。表示线程调度的优先级,优先级越高,获得CPU的执行机会就越大。 (4)线程状态。表示当前线程的执行状态,为新建、就绪、运行、阻塞、结束等状态中的一种。 (5)其他。例如是否为守护线程等,后面会详细介绍。在线程的结构中,程序计数器很重要,它记录着线程下一条指令的代码段内存地址。
程序计数器记录着线程下一条指令的代码段内存地址。
进程与线程的区别
(1)线程是“进程代码段”的一次顺序执行流程。一个进程由一个或多个线程组成,一个进程至少有一个线程。 (2)线程是CPU调度的最小单位,进程是操作系统分配资源的最小单位。线程的划分尺度小于进程,使得多线程程序的并发性高。 (3)线程是出于高并发的调度诉求从进程内部演进而来的。线程的出现既充分发挥了CPU的计算性能,又弥补了进程调度过于笨重的问题。 (4)进程之间是相互独立的,但进程内部的各个线程之间并不完全独立。各个线程之间共享进程的方法区内存、堆内存、系统资源(文件句柄、系统信号等)。 (5)切换速度不同:线程上下文切换比进程上下文切换要快得多。所以,有的时候,线程也称为轻量级进程。
创建线程的四种方法
Thread类简介
Thread具有id、name、priority、deamon、threadStatus、启动和运行、获取线程 等属性和方法。
简述几个常用的
priority
public final void setPriority(intpriority),设置线程优先级。Java线程的最大优先级值为10,最小值为1,默认值为5。
deamon
守护线程,是在进程运行时提供某种后台服务的线程,比如垃圾回收(GC)线程。
threadStatus
public Thread.State getState(),返回表示当前线程的执行状态,Thread的内部静态枚举类State用于定义Java线程的所有状态,具体如下:NEW(新建),RUNNABLE(就绪、运行),BLOCKED(阻塞),WAITING(等待),TIMED_WAITING(计时等待),TERMINATED(结束)
Java线程状态中,就绪状态和运行状态在内部都用RUNNABLE。就绪状态表示线程具备运行条件,正在等待获取CPU时间片;运行状态表示线程已经获取了CPU时间片,CPU正在执行线程代码逻辑。
start 和 run
public void start()
用来启动一个线程 ,当调用start()方法后,JVM才会开启一个新的线程来执行用户定义的线程代码逻辑,在这个过程中会为相应的线程分配需要的资源。
public void run()
作为线程代码逻辑的入口方法 。run()方法不是由用户程序来调用的,当调用start()方法启动一个线程之后,只要线程获得了CPU执行时间,便进入run()方法体去执行具体的用户线程代码。
创建线程
创建一个空线程
public class EmptyThreadDemo
{
public static void main(String args[]) throws InterruptedException
{
Thread thread = new Thread();
Print.cfo("线程名称:" + thread.getName());
Print.cfo("线程ID:" + thread.getId());
Print.cfo("线程状态:" + thread.getState());
Print.cfo("线程优先级:" + thread.getPriority());
thread.start();
Print.cfo("线程状态:" + thread.getState());
ThreadUtil.sleepMilliSeconds(10);
}
}
[EmptyThreadDemo.main]:线程名称:Thread-0 [EmptyThreadDemo.main]:线程ID:19 [EmptyThreadDemo.main]:线程状态:NEW [EmptyThreadDemo.main]:线程优先级:5 [EmptyThreadDemo.main]:线程状态:RUNNABLE
thread调用start()方法,新线程会去调用run()方法,注意thread中的run()方法:
public void run() {
if (target != null) {
target.run();
}
}
target是Thread类的一个实例属性,默认为空。在这个例子中,thread线程的target属性默认为null。所以在thread线程执行时,其run()方法其实什么也没有做,线程就执行完了。
继承Thread类
(1)需要继承Thread类,创建一个新的线程类。 (2)同时重写run()方法,将需要并发执行的业务代码编写在run()方法中。
public class CreateDemo {
public static final int MAX_TURN = 5;
static int threadNo = 1;
static class DemoThread extends Thread {
public DemoThread() {
super("Mall-" + threadNo++);
}
public void run() {
Print.cfo(getName() + ", 运行");
}
}
public static void main(String args[]) throws InterruptedException {
new DemoThread().start();
new DemoThread().start();
Print.cfo(getCurThreadName() + " 运行结束.");
}
}
实现Runnable接口
Thread类中的run方法
package java.lang;
public class Thread implements Runnable {
...
private Runnable target;
public void run() {
if (target != null) {
target.run();
}
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
}
public class CreateDemo2 {
public static final int MAX_TURN = 3;
static int threadNo = 1;
static class RunTarget implements Runnable
{
public void run()
{
for (int j = 1; j < MAX_TURN; j++) Print.cfo(getCurThreadName() + ", 轮次:" + j);
Print.cfo(getCurThreadName() + " 运行结束.");
}
}
public static void main(String args[]) throws InterruptedException {
for (int i = 0; i < 2; i++) {
Runnable target = new RunTarget();
new Thread(target, "RunnableThread" + threadNo++).start();
}
}
}
[CreateDemo2$RunTarget.run]:RunnableThread1, 轮次:1 [CreateDemo2$RunTarget.run]:RunnableThread2, 轮次:1 [CreateDemo2$RunTarget.run]:RunnableThread1, 轮次:2 [CreateDemo2$RunTarget.run]:RunnableThread2, 轮次:2 [CreateDemo2$RunTarget.run]:RunnableThread2 运行结束. [CreateDemo2$RunTarget.run]:RunnableThread1 运行结束.
值得注意的是,run()方法实现版本中在获取当前线程的名称时,所用的方法是在外部类ThreadUtil中定义的getCurThreadName()静态方法,而不是Thread类的getName()实例方法。原因是:这个RunTarget内部类和Thread类不再是继承关系,无法直接调用Thread类的任何实例方法。
通过实现Runnable接口的方式创建的执行目标类,如果需要访问线程的任何属性和方法,必须通过Thread.currentThread()获取当前的线程对象,通过当前线程对象间接访问。
通过继承Thread类的方式创建的线程类,可以在子类中直接调用Thread父类的方法访问当前线程的名称、状态等信息。这也是使用Runnable实现异步执行与继承Thread方法实现异步执行不同的地方。
实现Runnable接口创建线程目标类的优点
(1)可以避免由于Java单继承带来的局限性。如果异步逻辑所在类已经继承了一个基类,就没有办法再继承Thread类。
(2)逻辑和数据更好分离。通过实现Runnable接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。在同一个资源被多个线程逻辑异步、并行处理的场景中,通过实现Runnable接口的方式设计多个target执行目标类可以更加方便、清晰地将执行逻辑和数据存储分离,更好地体现了面向对象的设计思想。
继承Thread类或者实现Runnable接口这两种方式来创建线程类,但是这两种方式有一个共同的缺陷:不能获取异步执行的结果。
使用Callable和FutureTask创建线程
使用Callable创建线程
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Callable接口是一个泛型接口,也是一个“函数式接口”。其唯一的抽象方法call()有返回值,返回值的类型为Callable接口的泛型形参类型。call()抽象方法还有一个Exception的异常声明,容许方法的实现版本的内部异常直接抛出,并且可以不予捕获。 Callable实例能否和Runnable实例不一样,不能作为Thread线程实例的target来使用呢。Thread的target属性的类型为Runnable,而Callable接口与Runnable接口之间没有任何继承关系,并且二者唯一的方法在名字上也不同。RunnableFuture接口在Callable接口与Thread线程之间起到搭桥作用。
RunnableFuture接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
RunnableFuture继承了Runnable接口,从而保证了其实例可以作为Thread线程实例的target目标;同时,RunnableFuture通过继承Future接口,保证了可以获取未来的异步执行结果。
Future接口
Future接口至少提供了三大功能: (1)能够取消异步执行中的任务。 (2)判断异步任务是否执行完成。 (3)获取异步任务完成后的执行结果。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask类
FutureTask类实现了RunnableFuture接口。RunnableFuture实现了Future接口和Runnable接口。FutureTask既能作为一个Runnable类型的target执行目标直接被Thread执行,又能作为Future异步任务来获取Callable的计算结果。
在FutureTask类的run()方法完成callable成员的call()方法的执行之后,其结果将被保存在 outcome 实例属性中,供FutureTask类的get()方法获取。
通过线程池创建
private static ExecutorService pool = Executors.newFixedThreadPool(3);
void execute(Runnable command);

<T> Future<T> submit(Callable<T> task); 

Future<?> submit(Runnable task);
execute(…)与submit(…)方法的区别如下:
(1)接收的参数不一样
submit()可以接收两种入参:1、无返回值的Runnable类型的target执行目标实例,2、有返回值的Callable类型的target执行目标实例。
execute()仅仅接收无返回值的target执行目标实例,或者无返回值的Thread实例。
(2)submit()有返回值,而execute()没有
submit()方法在提交异步target执行目标之后会返回Future异步任务实例,以便对target的异步执行过程进行控制,比如取消执行、获取结果等。
execute()没有任何返回,target执行目标实例在执行之后没有办法对其异步执行过程进行控制,只能任其执行结束。
|