一个进程由程序段
、数据段
和进程控制块
三部分组成。程序段
也称为代码段,是进程的程序指令;数据段是进程的操作数据在内存中的位置;进程控制端(PCB)
包含进程的描述信息
(如进程ID和进程名称,进程状态。进程优先级)、控制信息
(如程序起始地址)、资源信息
(内存信息,设备信息,文件句柄)、进程上下文
(CPU寄存器的值、程序计数器PC的值),是进程存在的唯一标识
。
各个进程之间不共享内存,但是线程之间会共享进程的方法区内存空间、堆内存、系统资源。
进程
是操作系统分配资源的最小单位,线程
是CPU
调度的最小单位。
每一个线程都有自己独立的栈内存
,JDK1.8
默认是1MB
,栈内存
和堆内存是不一样的,栈内存不会被GC回收
。栈内存分配的基本单位是栈帧
,线程每进入一个方法,就会分配一个栈帧,栈帧保存着方法中的局部变量、方法的返回值以及方法的其他信息
。当方法退出后,栈内存就会弹出该栈帧,进而释放内存空间。
继承Thread类,创建一个新的线程类,同时重写run方法。
实例化该类,调用start方法启动线程
public class MyThread extends Thread{
//线程的编号
static int threadNo = 1;
public MyThread() {
super("DemoThread-" + threadNo++);
}
public void run() {
for (int i = 1; i < 2; i++) {
System.out.println(getName() + "轮次" + i);
}
System.out.println(getName() + "运行结束");
}
}
@Test
public void testCreateThread() throws InterruptedException {
Thread thread = null;
//方法一:使用Thread子类创建和启动线程
for (int i = 0; i < 2; i++) {
thread = new MyThread();
thread.start();
}
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 运行结束.");
}
public class Thread2 implements Runnable{
@Override
public void run() {
System.out.println("我是通过实现Runnable接口创建的线程");
}
}
@Test
public void testThread2() throws InterruptedException {
Thread thread = new Thread(new Thread2(), "线程2");
thread.start();
Thread.sleep(1000);
}
或者可以直接传入Runnable接口匿名实现类
,还可以使用lambda表达式
@Test
public void testThread2() throws InterruptedException {
// Thread thread = new Thread(new Thread2(), "线程2");
// 使用lambda表达式
// Thread thread = new Thread(() -> System.out.println("我是匿名实现类"));
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是匿名实现类");
}
});
thread.start();
Thread.sleep(1000);
}
如果使用第一种方法创建线程,创建线程时需要new Thread,如果采用继承方法,那么每个线程的数量都是各自享有的,不能共同合作;如果采用实现Runnable接口,创建线程时,可以多个线程共享一个Runnable接口实例,从而多个线程共享数据,相互合作。
实现Runnable接口的方法本质上是Thread的run方法源码会判断自己的target域是否为null
,如果不为空,则执行target.run()方
法,而target域就是我们传入的Runnable接口实例
。
前面实现Runnable接口不能有返回值
,如果新的线程需要有返回值,需要实现Callable接口
。但是Callable接口跟Runnable接口没什么关系,所以不能作为new Thread(Runnable run)
的传入参数。所以为了桥接Callable接口和Runnable接口
,定义了一个新的接口RunnableFuture接
口,该接口继承了Runnable接口
使其可以作为Thread类的传入参数)和Future<>接口
(1、取消异步任务,2、判断任务是否执行完毕,3、返回异步任务执行结果)。
FutureTask
类实现了RunnableFuture
接口,可以作为Thread的传入参数,同时他又拥有一个callable
实例,用来执行任务。FutureTask
内部还有另一个非常重要的Object 类型的成员——outcome实例属性,用来保存返回结果。
所以整个执行流程为:
首先为了声明一个异步任务,我们需要实现Callable
接口的call
方法,这是第一步,声明了任务。
可是如何新建线程执行该任务呢?Callable
接口实现类并不能作为Thread的target;于是我们把实现Callable接口的任务类传递给了FutureTask
类,FutureTask
实现了RunnableFuture
接口,该接口继承了Runnable和Future接口,可以作为Thread类的target。
从直觉上讲,Runnable
和Callable
接口的地位一致,Future
接口是为了管理Callable
接口返回值锁增加的功能增强接口,它们两个总是成对出现。关系如下:
@Test
public void testThread3() throws ExecutionException, InterruptedException {
// 将Callable接口实例传入FutureTask类
FutureTask<Long> futureTask = new FutureTask<>(() -> {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " 线程运行开始.");
Thread.sleep(1000);
for (int i = 0; i < 200; i++) {
int j = i * 10000;
}
long used = System.currentTimeMillis() - startTime;
return used;
});
Thread thread = new Thread(futureTask);
thread.start();
// 等子线程执行完毕后返回
Long res = futureTask.get();
System.out.println(res);
}
如果子线程没执行完毕,主线程就调用get()函数获取执行结果,那么主线程会阻塞直到子线程执行完毕
可以将一个Runnable接口匿名类
或者一个Callable接口匿名类
提交给线程池对象
来创建线程。
线程池对象由线程池工厂创建。
submit()
方法只需要传入Callable
接口,不需要创建FutureTask
对象,这是因为submit
方法内部会创建FutureTask
对象。@Test
public void testExecutor() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
// 通过execute方法向线程池提交一个runnable接口匿名类,不带返回值
executorService.execute(() -> {
System.out.println("我是Runnable接口线程");
});
Future<Integer> future = executorService.submit(() -> {
System.out.println("我是Callable或者Runnable接口线程");
return 2;
});
// 获取返回结果
System.out.println(future.get());
}
Thread.sleep()
:使线程睡眠,从运行态转为阻塞态Thread.interrupt()
:将线程的interrupt标志设为true,可以通过isInterrupt()
方法判断是否中断。interrupt()
方法只是会改变线程中断状态,不会真正中断一个线程,仍需要用户调用isInterrupted()
方法进行监控后处理。Thread.join()
:join()方法是实例方法,需要使用被合并线程的句柄(或者指针、变量)去调用, 主线程必须等待被合并线程执行完毕后才能继续执行Thread.yield()
:让线程由运行态转为就绪状态,并不会阻塞该线程