目录
一、认识线程(Thread)
1、什么是线程?
一个线程就是一个“执行流”,每个线程之间都可以按照顺序执行自己的代码。多个线程之间“同时”执行着多份代码。
线程的实现方式:
- 内核线程→Java中的线程,是基于内核线程的轻量级实现(轻量级进程:相比于进程,创建、调度、销毁效率要高的多)
- 用户线程:用程序自己来实现线程,包括线程的调度等。(安卓以前的开发语言时Java,最新的是kotlin(运行在jvm上),里面使用的就是协议(用户线程))
2、线程和进程的关系
- 进程与进程之间不共享内存空间,同一个进程的线程之间共享同一个内存空间。
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
- 线程的创建、调度、销毁的代价小于进程。
- 进程包含线程,每个进程至少存在一个线程,即主线程。
- 线程(出现bug可能导致整个进程挂掉),进程间是独立运行的(可能存在进程通信)
3、多线程的优势
多线程也存在其缺陷:线程的创建/销毁也有一定的开销(一般用于执行耗时比较长的任务);增加了编码的复杂程度。
“并发编程”成为刚需。
- 充分利用cpu资源,提高执行效率。(单核cou的发展遇到瓶颈,要想提高计算力,就需要多核cpu。并发编程能充分利用多核cpu资源)
- 有些任务场景需要“等待IO”,使用并发编程此时可以去做其他的事情。
4、线程的状态
![](https://img-blog.csdnimg.cn/5f822ec5b86740e681a8d470ed30ea60.png)
- new:创建态
- runnable:可运行态
(程序无法判断某个时间是就绪态还是可运行态,所以这两个状态对程序没有意义) - 等待、超时等待、阻塞。(这三种状态的特征都是程序在挂起来等)
- 销毁
5、创建线程的方式
(1)继承Thread类
- 继承Thread来创建一个线程类
public static void main(String[] args) { //继承的写法1.自定义一个类来继承Thread //创建一个线程 MyThread myThread = new MyThread(); //运行一个线程:申请系统调度运行 myThread.start(); } //继承的方式:1.继承Thread,2.重写run方法(定义要执行的任务代码) private static class MyThread extends Thread{ @Override public void run() { System.out.println("mythread run"); } }
- 使用匿名内部类创建Thread子类对象
public static void main(String[] args) { //继承的写法2.使用一个匿名内部类 Thread t = new Thread(){//属于继承Thread但没有名称的子类 @Override public void run() { System.out.println("匿名内部类 run"); } }; t.start(); }
(2)实现Runnable
- 实现Runnable接口
public static void main(String[] args) { //实现Runnable写法1 Thread t1 = new Thread(new MyRunnable()); t1.start(); } private static class MyRunnable implements Runnable{ @Override public void run() { System.out.println("MyRunnable run"); } }
- 匿名内部类创建Runnable子类对象
//实现Runnable写法2:匿名内部类 Runnable r2 = new Runnable() {//属于Runnable接口的实现类(没有名字) @Override public void run() { System.out.println("匿名内部类run"); } }; Thread t2 = new Thread(r2); t2.start(); //也可以把匿名内部类对象,直接写在构造方法的参数上 Thread t3 = new Thread(new Runnable() { @Override public void run() { System.out.println("匿名内部类run"); } }); t3.start();
- 使用lambda表达式创建子类对象
//了解:lambda表达式
Thread t4 = new Thread(() -> System.out.println("匿名内部类run"));
t4.start()
(3)实现Callable接口
二、Thread类及其常用方法
1、Thread常见的构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
2、启动线程
(1)start
thread.start();
//申请系统调度,执行thread中的任务(重写的run方法)
main线程创建Thread的代码,没有执行重写的run方法。
(2)start()和run()的区别
- start:启动线程的方式;
- run:属于线程任务的描述。
(3)多个线程,同时存在并发和并行的现象
观察以下代码:
main线程在执行到t.start()时,才会启动t线程,t线程的run中代码行和main线程的打印代码行执行顺序是随机的/无序的(但t线程启动后,系统调度才会执行run方法,这是过程是需要耗时的,所以先打印main的代码行)。
3、线程的属性
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID是线程的唯一标识,不同线程不会重复
- 状态表示当前线程所处的情况
- 关与后台线程:JVM会在一个进程的所有非后台线程结束后,才会结束运行
4、Thread常用方法
(1)等待一个线程--join()
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
使用join后,两个并发并行的线程随即执行的方式,就变成了有序。
(2)获取当前线程引用
public static Thread CurrentThread();
//返回当前线程对象的引用
(3)休眠当前线程
让当前线程休眠(超时等待)给定时间(毫秒)。因为线程的调度是不可控的,这个方法只能保证实际休眠时间大于等于参数设置的休眠时间。
方法 | 说明 |
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
使用场景:进行程序的调试;控制多个线程的执行顺序。
(4)中断线程
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置中断标志位为true。 |
public static boolean interrupt() | 判断当前线程的中断标志位是否设置,调用后清除标志位(会重置中断标志位) |
public static boolean isInterrupt() | 判断对象关联的线程标志位是否设置,调用后不清除标志位 |
【注】
- Java中断,是以中断标志位的方式执行的;
- interrupt是发起中断的动作,但线程是否中断,有代码是否判断中断标志位来决定;
- 线程处于某些等待/超时等待的状态(会显式的抛InterruptException异常),都是允许被中断的,中断的方式是:(1)抛异常来进行中断(2)抛异常后,会重置中断标志位(默认false)
三、多线程(内存的使用)
Java进程的内存:栈、堆、方法区、程序计数器。