线程(Thread)
计算机的核心是CPU,它承担了计算机的所有计算任务,CPU就像一个工厂,时刻在运行着,而操作系统管理着计算机,负责任务的调度、资源的分配和管理。
概念:
什么是程序:
例如,我们的桌面上都会安装QQ、酷狗音乐、微信…等,这些就是程序。当我们点击QQ运行时,QQ正常运行,此时就会开启一个进程。
什么是进程:
进程指在系统中正在运行的一个应用程序;程序一旦运行就是进程。
程序是静态的,而进程是动态的,程序是作为进程的运行的载体,进程会随时间,会在某一时刻消亡。
什么是线程:
线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程——系统资源分配的最小单位;线程——CPU调度的最小单位;
一个进程内的线程之间是可以共享资源的;每个进程至少有一个线程存在,即主线程。
对比线程和进程
线程的优点:
1. 创建一个新线程的代价要比创建一个新进程小得多
2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3. 线程占用的资源要比进程少很多
4. 能充分利用多处理器的可并行数量
5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
7. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
进程与线程的区别:
1. 进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位。
2. 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。
3. 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。
4. 线程的创建、切换及终止效率更高。
那为什么有进程还要多线程呢?
每个进程都有自己独自的代码和数据空间,即为「程序的上下文」,进程包含多个线程,进程的切换消耗要大于线程的切换消耗。
线程可以看作是轻量级的进程,每个线程也有自己的「运行栈」和「程序计数器(PC)」、以及「线程的本地存储」,所以对于进程数比较多的,频繁的切换进程将会带来一大笔的开销,反而线程的切换开销小。
创建线程
方法1:继承 Thread 类
通过继承 Thread 来创建一个线程类。
该方法的好处是 this 代表的就是当前线程,不需要通过 Thread.currentThread() 来获取当前线程的引用。
class MyThread extends Thread{
@Override
public void run(){
System.out.println(this.getName());
}
}
public class Demo {
public static void main(String[] args) {
// 创建并启动线程
MyThread t=new MyThread();
t.start();
}
}
方法2:实现 Runnable 接口
通过实现 Runnable 接口,并且调用 Thread 的构造方法时将 Runnable 对象作为 target 参数传入来创建线程对象。
该方法的好处是可以规避类的单继承的限制;但需要通过 Thread.currentThread() 来获取当前线程的引用。
class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread().getName()
+"这里是线程运行的代码");
}
}
public class Demo1 {
public static void main(String[] args) {
// 创建并启动线程
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();
}
}
Thread 类及常见方法
Thread 类是 JVM 用来管理线程的一个类,即:每个线程都有一个唯一的 Thread 对象与之关联。
每个执行流,需要有一个对象来描述,类似下图所示,
而 Thread 类的对象就是用来描述 一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
1. Thread 的常见构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target,String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
2. Thread 的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
ID 是线程的唯一标识,不同线程不会重复
名称是各种调试工具用到
状态表示线程当前所处的一个情况
优先级高的线程理论上来说更容易被调度到
后台线程:JVM会在一个进程的所有非后台线程结束后,才会结束运行
是否存活,即 run 方法是否运行结束了
线程的中断问
例子:
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++){
try {
System.out.println(Thread.currentThread().getName() + ": 我还活着");
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": 我即将死去");
});
System.out.println(Thread.currentThread().getName()
+ ": ID: " + thread.getId());
System.out.println(Thread.currentThread().getName()
+ ": 名称: " + thread.getName());
System.out.println(Thread.currentThread().getName()
+ ": 状态: " + thread.getState());
System.out.println(Thread.currentThread().getName()
+ ": 优先级: " + thread.getPriority());
System.out.println(Thread.currentThread().getName()
+ ": 后台线程: " + thread.isDaemon());
System.out.println(Thread.currentThread().getName()
+ ": 活着: " + thread.isAlive());
System.out.println(Thread.currentThread().getName()
+ ": 被中断: " + thread.isInterrupted());
thread.start();
while (thread.isAlive()) {}
System.out.println(Thread.currentThread().getName()
+ ": 状态: " + thread.getState());
}
}
3. 启动一个线程-start()
线程对象被创建出来并不意味着线程就开始运行了
注意:run 方法和 start 方法是不同的,启动线程必须要调用 start 方法。
4.中断一个线程
通过共享的标记来进行沟通
调用 interrupt() 方法来通知
示例:
public class Demo2 {
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;
}
}
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();
}
}
1. 通过 thread 对象调用 interrupt() 方法通知该线程停止运行
2. thread 收到通知的方式有两种:
1.如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,
清除中断标志
2.否则,只是内部的一个中断标志被设置,thread 可以通过
1.Thread.interrupted()
判断当前线程的中断标志被设置,清除中断标志
2.Thread.currentThread().isInterrupted()
判断指定线程的中断标志被设置,不清除中断标志
这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。
方法 | 说明 |
---|---|
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
5.等待一个线程-join()
等待一个线程完成它的工作后,才能进行自己的下一步工作。
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName()
+ ": 我还在工作!");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ ": 我结束了!");
};
Thread thread1 = new Thread(target, "李四");
Thread thread2 = new Thread(target, "王五");
System.out.println("先让李四开始工作");
thread1.start();
thread1.join();
System.out.println("李四工作结束了,让王五开始工作");
thread2.start();
thread2.join();
System.out.println("王五工作结束了");
}
}
6. 获取当前线程引用
方法 | 说明 |
---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
7. 休眠当前线程
因为线程的调度是不可控的,所以,这个方法只能保证休眠时间是大于等于休眠时间的。
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
状态
线程的状态
初始态,执行态,等待状态,就绪状态,终止状态
线程的状态
单线程时代,一次只能执行一个任务,后面的任务只能排队等候,实现的方式都是串行化的,随着后续的发展为了提高效率,实现了多线程。
单CPU的多线程方式,实现的是并发方式,并发真正意义上的并行,因为CPU一次只能执行一次任务,但是,CPU的执行速度远快于线程的执行速度,为了充分利用CPU,因此实现并发的多线程方式。