目录
认识线程
概念
什么是线程?
为啥要有线程
进程和线程的区别
Java 的线程 和 操作系统线程 的关系
创建线程
1.继承 Thread 类
2.实现 Runnable 接口
3.通过匿名内部类方式创建Thread与实现Runnable
4.Lmabda表达式
Thread 类及常见方法
Thread 的常见构造方法
Thread 的几个常见属性
启动一个线程-start()
中断一个线程
等待一个线程-join()
获取当前线程引用
休眠当前线程
线程的状态
观察线程的所有状态
认识线程
概念
什么是线程?
一个线程就是一个 " 执行流 ". 每个线程之间都可以按照顺讯执行自己的代码 . 多个线程之间 " 同时 " 执行着多份代码。
多进程可以处理一个很大或复杂的任务,但启动进程时申请内存,申请文件资源,进程结束时释放文件,释放内存的操作非常耗时。
为了解决资源消耗问题,提出一个轻量化进程的概念(线程),创建线程时只关注要处理的任务,使用的是进程创建时申请的所有资源(类比开工厂)。
为了解决资源消耗问题,提出一个轻量化进程的概念(线程),创建线程时只关注要处理的任务,使用的是进程创建时申请的所有资源(类比开工厂)。
例:进程就相当于开工厂新建一个场子,需要购买地皮、拉电拉水、修建仓库等,非常的费时间;线程就相当于在原有工厂资源的基础上重新开一条生产线,不需要前期的资源申请,节省时间,更好的利用资源。
为啥要有线程
首先
, "
并发编程
"
成为
"
刚需
".
- 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源.
- 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.
其次
,
虽然多进程也能实现 并发编程
,
但是线程比进程更轻量
.
- 创建线程比创建进程更快.
- 销毁线程比销毁进程更快.
- 调度线程比调度进程更快.
最后
,
线程虽然比进程轻量
,
但是人们还不满足
,
于是又有了
"
线程池
"(ThreadPool)
和
"
协程
"
(Coroutine)
进程和线程的区别
- 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
- 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
Java 的线程 和 操作系统线程 的关系
线程是操作系统中的概念
.
操作系统内核实现了线程这样的机制
,
并且对用户层提供了一些
API
供用户使用(
例如
Linux
的
pthread
库
).
Java
标准库中
Thread
类可以视为是对操作系统提供的
API
进行了进一步的抽象和封装
.
创建线程
1.继承 Thread 类
public class MThread {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("这是线程运行代码");
}
}
2.实现 Runnable 接口
public class MRunnable {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("这是线程运行代码");
}
}
使用Runnable定义的好处:
- 解耦,把定义线程与定义任务分开,以便修改代码统一修改
对比上面两种方法
:
- 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
- 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()
3.通过匿名内部类方式创建Thread与实现Runnable
Thread
public class Thread_Anon {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("这是线程运行代码");
}
};
thread.start();
}
}
Runnable
public class Runnable_Anon {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("这是线程执行代码");
}
});
thread.start();
}
}
4.Lmabda表达式
public class lambda {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("这是线程执行代码");
});
thread.start();
}
}
用Lambda表达式实现的接口必须是函数式接口(接口中只有一个没有实现的方法)
使用多线程编程主要是为了充分利用CPU资源,提升程序运行效率;
但并不是所有场景使用多线程都可以提高效率
Thread 类及常见方法
Thread
类是
JVM
用来管理线程的一个类,换句话说,每个线程都有一个唯一的
Thread
对象与之关联。
每个执行流,也需要有一个对象来描述,而
Thread
类的对象就是用来描述一个线程执行流的,JVM
会将这些
Thread
对象组织起来,用于线程调度,线程管理。
Thread 的常见构造方法
Thread 的几个常见属性
属性
| 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,即简单的理解,为 run 方法是否运行结束了
- 线程的中断问题
启动一个线程-start()
之前我们已经看到了如何通过覆写
run
方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。
覆写
run
方法是提供给线程要做的事情的指令清单
线程对象可以认为是把 李四、王五叫过来了
而调用
start()
方法,就是喊一声:
”
行动起来!
“
,线程才真正独立去执行了。
调用
start
方法
,
才真的在操作系统的底层创建出一个线程
.
中断一个线程
李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉及到我们的停止线程的方式了。
目前常见的有以下两种方式:
- 通过共享的标记来进行沟通
- 调用 interrupt() 方法来通知
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
// 两种方法均可以
while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()
+ ": 有内鬼,终止交易!");
// 注意此处的 break
break;
}
}
System.out.println(Thread.currentThread().getName()
+ ": 啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");
thread.interrupt();
}
}
thread 收到通知的方式有两种:
1.
如果线程因为调用
wait/join/sleep
等方法而阻塞挂起,则以
InterruptedException
异常的形式通
知,
清除中断标志
-
当出现 InterruptedException 的时候 , 要不要结束线程取决于 catch 中代码的写法 . 可以选择忽略这个异常 , 也可以跳出循环结束线程 .
2.
否则,只是内部的一个中断标志被设置,
thread
可以通过
- Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
- Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
等待一个线程-join()
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。
获取当前线程引用
方法
|
说明
|
public static Thread currentThread();
|
返回当前线程对象的引用
|
休眠当前线程
因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。
方法
|
说明
|
public static void sleep(long millis) throws InterruptedException
|
休眠当前线程
millis
毫秒
|
public static void sleep(long millis, int nanos) throws
InterruptedException
|
可以更高精度的休眠
|
线程的状态
观察线程的所有状态
线程的状态是一个枚举类型
Thread.State
public class ThreadState {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}
- NEW: 安排了工作, 还未开始行动
- RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
- BLOCKED: 这几个都表示排队等着其他事情
- WAITING: 这几个都表示排队等着其他事情
- TIMED_WAITING: 这几个都表示排队等着其他事情
- TERMINATED: 工作完成了.
这里只是线程的部分知识,剩下的知识总结我会后面继续发布,期待大家关注