文章目录
- 线程
- 线程与进程的区别
- 创建线程的方法
- 方法一:继承Thread类
- 方法二:实现Runnable接口
- 方法三:使用Callable和FutureTask创建带返回值的线程
- 方法四:通过线程池创建线程
- 线程的基本操作
- 线程的状态
- 守护线程
线程
线程与进程的区别
一个进程由程序段
、数据段
和进程控制块
三部分组成。程序段
也称为代码段,是进程的程序指令;数据段是进程的操作数据在内存中的位置;进程控制端(PCB)
包含进程的描述信息
(如进程ID和进程名称,进程状态。进程优先级)、控制信息
(如程序起始地址)、资源信息
(内存信息,设备信息,文件句柄)、进程上下文
(CPU寄存器的值、程序计数器PC的值),是进程存在的唯一标识
。
各个进程之间不共享内存,但是线程之间会共享进程的方法区内存空间、堆内存、系统资源。
进程
是操作系统分配资源的最小单位,线程
是CPU
调度的最小单位。
每一个线程都有自己独立的栈内存
,JDK1.8
默认是1MB
,栈内存
和堆内存是不一样的,栈内存不会被GC回收
。栈内存分配的基本单位是栈帧
,线程每进入一个方法,就会分配一个栈帧,栈帧保存着方法中的局部变量、方法的返回值以及方法的其他信息
。当方法退出后,栈内存就会弹出该栈帧,进而释放内存空间。
创建线程的方法
方法一:继承Thread类
-
继承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() + " 运行结束.");
}
方法二:实现Runnable接口
- 新建类实现Runnable接口
- 将实现Runnable接口的类传入Thread类中,作为Thread类的目标方法
public class Thread2 implements Runnable{@Overridepublic 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() {@Overridepublic void run() {System.out.println("我是匿名实现类");}});thread.start();Thread.sleep(1000);
}
如果使用第一种方法创建线程,创建线程时需要new Thread,如果采用继承方法,那么每个线程的数量都是各自享有的,不能共同合作;如果采用实现Runnable接口,创建线程时,可以多个线程共享一个Runnable接口实例,从而多个线程共享数据,相互合作。
实现Runnable接口的方法本质上是Thread的run方法源码会判断自己的target域是否为null
,如果不为空,则执行target.run()方
法,而target域就是我们传入的Runnable接口实例
。
方法三:使用Callable和FutureTask创建带返回值的线程
前面实现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接口匿名类
提交给线程池对象
来创建线程。
线程池对象由线程池工厂创建。
- execute方法不带返回值
- submit方法带返回值:可以看到
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()
:让线程由运行态转为就绪状态,并不会阻塞该线程
线程的状态
- 新建:新建状态
- 就绪:等待线程调度
- 运行:运行中
- 阻塞:不满足某种条件,被挂起
- 结束:线程结束
守护线程
- 守护线程必须在调用start方法前设置
- 当main函数退出后,守护线程也会退出。如果JVM进程终止,守护线程也会被强制关闭
- 守护线程创建的线程也是守护线程