概念
多线程概念
- 进程(process):程序的运行实例,是程序向操作系统申请资源(比如内存空间和文件句柄)的基本单位;
- 线程(Thread):
- 进程中可独立执行的单位;
- 一个进程可以包含多个线程;
- 同一进程中的所有线程共享该进程中的资源,如内存空间,文件句柄等;
- 进程中可独立执行的单位;
- 线程分类:JVM用户线程与内核线程是1比1的关系
- 用户线程:不需要操作系统管理,自行创建,优点是性能更好,缺点是无法充分利用多核CPU的优势,且多线程并不是真正意义上的多线程,而是受CPU内核时间片管理的影响
- 内核线程:由操作系统管理的线程,能够充分利用多CPU的特点,缺点是线程切换耗时
- 用户线程:不需要操作系统管理,自行创建,优点是性能更好,缺点是无法充分利用多核CPU的优势,且多线程并不是真正意义上的多线程,而是受CPU内核时间片管理的影响
- 任务:线程要完成的计算就被称为任务,特定的线程总是在执行着特定的任务
串行、并行、并发
- 串行:多个任务依次执行
- 并发(Concurrent):先做任务A,然后A准备就绪,开始等待;在等待A完成的时候,开始准备任务B,任务B准备就绪后,开始等待;在等待任务B完成时开始任务C的准备,然后等待3个任务的完成
- 并行(Parallel):多任务同时进行,总耗时由需要时间最长的任务
编程范式
- 函数式编程(Functional Programming):函数式基本抽象单位;
- 面向对象编程:类是基本抽象单位;
- 多线程编程:以线程为基本抽象单位的一种编程范式(Paradigm);
线程创建、启动与相关属性
线程创建
Java标准库类 java.lang.Thread 就是Java平台对线程的实现。Thread类或其子类的一个实例就是一个线程。
线程创建就是创建一个Thread类或其子类的实现,线程的任务逻辑放在Thread的run()方法中。Thread常用构造器:Thread() 与 Thread(Runnable target)
创建线程的三种方式:
1、继承 Thread 类
public class Task extends Thread {
@Override
public void run() {
System.out.println("任务执行!");
}
public static void main(String[] args) {
Task task = new Task();
task.start();
}
}
2、实现 Runnable 接口
public class Task implements Runnable{
@Override
public void run() {
System.out.println("任务执行!");
}
public static void main(String[] args) {
Thread task = new Thread(new Task());
task.start();
}
}
3、实现 Callable
线程启动
通过 Thread 类的 start() 方法启动相应线程,实质是请求 java 虚拟机运行相应的线程,该线程何时运行由线程调度器 Scheduler(操作系统一部分)决定。start() 方法调用结束不意味相应线程开始运行。
注意:
- Thread.start():方法会调用本地方法,创建新线程执行 run() 方法
- Thread.run():方法是在当前线程中执行 run() 方法,不会创建新线程【实现线程池的关键方法】
线程属性
常见的线程属性有如下几个:
- 编号,ID
- 名称,Name
- 线程类别,Daemon
- 优先级,Priority
1、线程ID:线程ID为 long 类型,用于标识不同的线程,该属性为只读属性
获取线程 ID 的方法:
// 获取当前线程的ID
long id = Thread.currentThread().getId();
// 获取某个指定线程的ID
Thread thread = new Thread();
long id = thread.getId();
线程 ID 相关细节:
- 某个编号的线程运行结束后,该编号可能会被后续创建的线程使用;
- 线程编号只在 Java 虚拟机中的一次运行有效,即某个线程运行时的编号在 JVM 重启后有可能发生改变,因此线程 ID 不适合作为唯一标识;
2、线程名称 name
- String 类型,默认的格式为:”Thread-线程ID“,属于可以自定义的属性
- 为了方便调试,尽量设置易懂的线程名 ```java // 在创建线程时设置线程name Thread thread = new Thread(“线程名”);
// 设置当前线程的线程名 Thread.currentThread().setName(“线程名”);
// 设置指定线程的线程名 Thread thread = new Thread(); thread.setName(“新的线程名”);
// 获取线程名 Thread.currentThread().getName();
3、线程类别 Daemon:类型 boolean,值为 true 表示相关线程为守护线程,否则表示相关线程为非守护线程,该属性的默认值与相应线程的父线程的该属性的值相同,该值可以自定义<br />注意事项:
1. 该属性必须在相应线程启动之前设置,即对 setDaemon 方法的调用必须在对 start 方法调用之前,否则 setDaemon 方法会抛出 IllegalThreadStateException 异常
1. 通过方法 Thread.currentThread().isDaemon() 可以获取当前线程是否为守护线程:
- true:当前线程为守护线程(Daemon Thread)<br />
- false:当前线程为用户线程(User Thread)<br />
3. 守护线程不会影响 Java 虚拟机的正常停止,一般用于执行一些重要性不是很高的任务(负责关键任务的线程不适合设置为守护线程),比如监视其他线程的运行情况;
3. **用户线程会阻止 Java 虚拟机的正常停止**,即一个Java虚拟机只有在其所有用户线程都运行结束(即 Thread.run() 调用未结束)的情况下才能正常停止;
3. Java虚拟机被强制停止时比如 Linux 的 kill 命令,用户线程也无法阻止 Java 虚拟机的停止;
4、线程优先级 Priority:类型为int,本质是一个给线程调度器的提示,用于表示希望那个线程能够优先得到运行。Java定义了1到10个优先级,默认值为5,表示普通优先级
```java
// 线程优先级常量
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
// 设置线程优先级
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
// 获取当前线程的优先级
int priority = Thread.currentThread().getPriority();
注意事项:
- 不正确的优先级设置可能会产生线程饥饿
- 优先级只是给线程调度器的提示信息,以便线程调度器决定优先调度哪个程序运行,并不能保证线程按照优先级给定的顺序运行
5、线程的层次关系(默认):
- 子线程是否是守护线程取决于父线程;
- 子线程的优先级为该线程的父线程优先级;
Thread 的常用方法
静态方法:
1、static Thread currentThread():返回当前线程,即当前代码的执行线程(对象)
Java 中任何一段代码总是执行在某个线程之中,执行当前代码的线程就被称为当前线程。Thread.currentThread() 可以返回当前线程,由于同一段代码可能被不同的线程执行,因此当前线程是相对的,即Thread.currentThread 的返回值在代码实际运行的时候可能对应着不同的线程(对象)。
public class SubThread extends Thread {
public SubThread() {
// 输出 Thread.currentThread()
System.out.println("Thread.currentThread():" +
Thread.currentThread().getName());
// 输出 this.getName()
System.out.println("This.getName():"+this.getName());
}
@Override
public void run() {
// 输出 Thread.currentThread()
System.out.println("Thread.currentThread():" +
Thread.currentThread().getName());
// 输出 this.getName()
System.out.println("This.getName():" + this.getName());
}
public static void main(String[] args) {
// 构造方法输出:
// Thread.currentThread():main
// This.getName():Thread-0
final SubThread thread = new SubThread();
thread.setName("测试线程");
// 运行时输出:
// Thread.currentThread():测试线程
// This.getName():测试线程
thread.start();
// 使用Thread(Runnable) 实现线程
Thread runnable = new Thread(thread);
// 运行时输出:即this指针的是赋值后的线程对象
// Thread.currentThread():Thread-1
// This.getName():测试线程
runnable.start();
}
}
2、static void sleep(long millis):是当前线程休眠(暂停运行)指定的时间
3、static void yield():使当前线程主动放弃其处理器的占用,这可能导致当前线程被暂停
注意:这个方法是不可靠的,该方法被调用时当前线程可能仍然继续运行(视系统的运行情况而定)
实例方法:
- void run():用于实现线程的任务处理逻辑,该方法一般由Java虚拟机直接调用,而不是应用程序调用
- void start():启动相应线程,该方法的返回并不代表相应的线程已经被启动,一个 Thread 实例的 start 方法只能被调用一次,多次调用会抛出异常
- void join():等待相应线程运行结束,即如果A线程调用B线程的join()方法,那么A线程的运行会被暂停,直到B运行结束
- isAlive():判断线程是否已经结束
执行结果:public static void main(String[] args) {
final Thread threadA = new Thread(() -> {
System.out.println("线程A开始执行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A执行完成");
});
final Thread threadB = new Thread(() -> {
System.out.println("线程B正在执行,然后调用ThreadA的join()方法");
try {
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B再次执行");
});
threadB.start();
threadA.start();
}
被废弃的方法:
- stop:中止线程运行
- suspend:暂停线程运行
- resume:与suspend连用,使被 suspend 方法暂停的线程继续运行
- destory:未实现的方法