多线程概要
多线程概要
什么是进程?
进程的特点:
什么是多线程
多线程编程:
创建线程
1.继承 Thread 类
2.实现 Runnable 接口
多线程的优势
中断问题:
1. 通过共享的标记来进行沟通
2. 调用 interrupt() 方法来通知
等待一个线程-join()
多线程概要
什么是进程?
在冯诺依曼体系下,整个计算机设备分为,应用程序,操作系统,处理器(cpu),主存,I/O设备。应用程序在操作系统的调节下在处理器上进行合理的资源分配,而在其中一个运行起来的程序,就是进程。
cpu有一个概念,核心数和线程,核心为物理核心,线程为逻辑核心
而进程管理其实分为两步:
1.描述一个进程:使用结构体或类,把一个进程有哪些信息,表示出来。
2. 组织这些进程:使用一定的数据结构,把这些结构体/对象,放在一起。
进程的特点:
- PID :每个进程需要有一个唯一的身份标识
- 内存指针:当前这个进程使用的内存是哪一部分,进程一旦开启,就会消耗一定的硬件资源
- 文件描述符:进程每次打卡一个文件,就会产生一个“文件描述符” ,被标识了的意味着这个文件已打开。而一个进程会打开多个文件,然后呢就会把这些文件描述符放到循序表中,构成文件描述符表
- 进程调度:
- 进程状态:就绪态,阻塞态,前者表示该进程已准备好,可以随时上cpu上执行,后者还需等待
- 进程优先级:那个进程优先级高就先执行那个进程
- 进程的上下文:就是描述了当前进程执行到哪里这样的“存档记录”,进程在离开CPU的时候就要把当前运行的中间结果存档在cpu的寄存器中,等到下次进程回来CPU上,在恢复之前的存档,从上次结果开始
- 进程的记账信息:统计了每个进程,在CPU上执行了多久,可以作为调度的参考依据。
- 并发和并行:指的是多个程序可以同时运行的现象,更细化的是多进程可以同时运行或者多指令可以同时运行。它们虽然都说是"多个进程同时运行",但是它们的"同时"不是一个概念。并行的"同时"是同一时刻可以多个进程在运行(处于running),并发的"同时"是经过上下文快速切换,使得看上去多个进程同时都在运行的现象,是一种OS欺骗用户的现象。
内存分配:
操作系统给进程分配的内存,是以“虚拟地址空间”的方式进行分配。
什么是多线程
之前说过,进程是一个运行程序,然而一个程序内的功能有很多个,而这其中就有一个问题,就是客户可能会同时用一个程序的多个功能。诺是按照以前我们的写发就是一个main方法,去实现一个主要功能,肯定是不行的。为了应对这个情况,多线程运行就在所难免。
需求决定技术发展
线程是更轻量的的进程。约定一个进程可以包含多个线程,此时多个线程每个线程都是一个独立可以调度执行的执行流(并发),这些线程公用同一份进程的系统资源。
- 创建线程比创建进程更快.
- 销毁线程比销毁进程更快.
- 调度线程比调度进程更快.
可以理解为,一个工厂(进程),中有很多个生产线:(线程)(调用同一份资源,内存空间,文件描述符)。
其中几个问题要重点理解。一个厂子也就意味着资源和场地是一定的,如果为了生产效率,盲目去增加生产线,不去顾忌这些,反而会使的整个生成效率变慢。同理一个主机的核心也是有限,所以增加的线程数和进程数也是有限度。而一台主机到限度了,就可以增加另一台主机,从而使得核心数增加(也就是分布式处理)。
进程和线程的区别:
1.进程包含线程
2.进程有自己独立的内存空间和文件描述符,同一个进程的多个线程之间,共享同一份地址空间和文件描述符
3.进程是操作系统资源分配的基本单位,线程是操作系统调度的基本单位
4.进程之间具有独立性,一个进程挂了,不会影响到别的进程;同一个进程里的多个线程之间,一线程挂了,可能会把整个进程带走,会影响到其他线程的。
多线程编程:
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装。
操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库)
这是我们第一个多线程。
public class ThreadDemo {private static class MyThread extends Thread {@Overridepublic void run() {Random random = new Random();while (true) {// 打印线程名称System.out.println(Thread.currentThread().getName());try {// 随机停止运行 0-9 秒Thread.sleep(random.nextInt(10));} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();Random random = new Random();while (true) {// 打印线程名称System.out.println(Thread.currentThread().getName());try {Thread.sleep(random.nextInt(10));} catch (InterruptedException e) {// 随机停止运行 0-9 秒e.printStackTrace();}}}
}
创建线程
1.继承 Thread 类
class MyThread extends Thread {@Overridepublic void run() {System.out.println("这里是线程运行的代码");}
}
创建一个线程实例
MyThread t = new MyThread();
调用 start 方法启动线程
t.start(); // 线程开始运行
2.实现 Runnable 接口
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("这里是线程运行的代码");}
}
创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数
Thread t = new Thread(new MyRunnable());
调用start方法
t.start(); // 线程开始运行
class MyRunnabble implements Runnable{@Overridepublic void run() {while (true){System.out.println("123__true");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class test1 {public static void main(String[] args) {MyRunnabble runnabble=new MyRunnabble();Thread t=new Thread(runnabble);t.start();while (true) {System.out.println("main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
- 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
- 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()
常见方法:
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
常见属性:
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("使用匿名类创建 Thread 子类对象");
}
};
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名类创建 Runnable 子类对象");
}
});
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t4 = new Thread(() -> {
System.out.println("使用匿名类创建 Thread 子类对象");
});
多线程的优势
1. 增加运行速度,通过记录时间戳
使用 System.nanoTime() 可以记录当前系统的纳秒级时间戳.
public class test3 {static int count=0;public synchronized static void sum(){count++;}public static void main(String[] args) throws InterruptedException {long time=System.currentTimeMillis();Thread t1=new Thread(()->{for (int i = 0; i < 10000; i++) {sum();}});Thread t2=new Thread(()->{for (int i = 0; i < 10000; i++) {sum();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);System.out.println(System.currentTimeMillis()-time);}
}
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况,下面我们会进一步说明
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,即简单的理解,为 run 方法是否运行结束了
- 线程的中断问题,下面我们进一步说明
中断问题:
一个程序要执行很多次,可是我们突然有了一个需求要添加,就得让这个程序停下来,所以我就需要线程中断。
目前常见的有以下两种方式:
1. 通过共享的标记来进行沟通
注意变量捕获,但是java中要求变量捕获,捕获的变量的必须要final或者“实际final”及没有用final修饰,但是代码中没有做出修改。
public class ThreadDemo {private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic void run() {while (!isQuit) {System.out.println(Thread.currentThread().getName()+ ": 转账");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}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()+ ": 通知李四对方是个骗子!");target.isQuit = true;}
}
2. 调用 interrupt() 方法来通知
使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定
义标志位.
public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {// 两种方法均可以while (!Thread.interrupted()) {//while (!Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName()+ ": 转账!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}}}}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(1000);System.out.println(Thread.currentThread().getName()+ ": 对方是个骗子!");thread.interrupt();}
}
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
thread 收到通知的方式有两种(这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到):
1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通
知,清除中断标志
- 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程.
2. 否则,只是内部的一个中断标志被设置,thread 可以通过
- Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
- Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
注意:interrupt():作用设置标志位true,如果该线程在阻塞中,此时会把阻塞状态唤醒,抛出异常的方式中断。(当sleep(也可看做阻塞)被唤醒会自动把interrupted,将标志位清空)
产生阻塞的方法,会使得看到标识位为true,任然会抛出异常和清空标志,如果设置interrupt的时候,阻塞巧合醒了,这个时候程序执行到下一个循环判断条件就结束了。
等待一个线程-join()
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。这样可以让线程变得可控。线程的调度是随机的,无法判定两个线程谁先结束,谁先开始。而jion就是确定谁先开始的方法。
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
public class test2 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(){@Overridepublic void run() {while (true) {System.out.println("123545_run");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};t.start();t.join();while (true) {System.out.println("123545_run");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了.
public class ThreadState {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}
BLOCKED 表示等待获取锁, WAITING 和 TIMED_WAITING 表示等待其他线程发来通知.
TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在无限等待唤醒