北京网站建设seo公司哪家好,常宁网站制作,谷歌广告推广网站,杭州制作网站个人目录 Day 3#xff1a;多线程#xff08;1#xff09;1. 线程1.1 引入线程的原因1.2 线程的定义1.3 为何线程更轻量1.4 问题 2. 多线程代码2.1 继承Thread重写run2.2 通过实现Runnable接口创建线程2.3 针对2.1的变形使用匿名内部类2.4 针对Runnable创建匿名内部类2.5 使用la… 目录 Day 3多线程11. 线程1.1 引入线程的原因1.2 线程的定义1.3 为何线程更轻量1.4 问题 2. 多线程代码2.1 继承Thread重写run2.2 通过实现Runnable接口创建线程2.3 针对2.1的变形使用匿名内部类2.4 针对Runnable创建匿名内部类2.5 使用lambda表达式 Day 3多线程1
C会对进程有更进一步介绍例如如何通过编写代码来进行进程的控制多进程编程但是Java并不太关注这些
JVM没有提供上述多进程编程的apiJava生态中也不太鼓励使用多进程编程 JVM也不是完全没有也提供了非常粗糙的多进程操作的api但是控制过程不如C通过系统原生api更精细 1. 线程
1.1 引入线程的原因
当前的CPU都是多核心CPU需要通过一些特定的编程技巧把要完成的任务拆解成多个部分并且分别让他们在不同的CPU核心上运行也就是**“并发编程”**
通过多进程编程的模式其实就可以起到“并发编程”的效果因为进程可以被调度到不同的CPU上运行此时就可以把多个CPU核心都给很好的利用起来虽然多进程编程可以解决上述问题也带来了新的麻烦在服务器开发中并发编程的需求场景非常常见所以一个服务器要能够同时给多个客户端提供服务如果同一时间来了很多客户端服务器如果只能利用一个CPU核心工作速度就会比较慢一种典型的做法每个客户端连上服务器了服务器都创建一个进程给客户端提供服务这个客户端断开了服务器再把进程给释放掉如果这个服务器频繁的有客户端来来去去服务器就需要频繁创建/销毁进程
所以引入线程来解决上述进程“太重量”的问题
1.2 线程的定义
线程thread也称为“轻量级进程”创建和销毁的开销更小线程可以理解成“进程的一部分”一个进程中可以包含一个线程或者多个线程描述进程使用PCB这样的结构体事实上更严格地说一个PCB其实是描述一个线程的若干个PCB联合在一起是描述一个进程的 PCBpid每个线程都不一样、内存指针、文件描述符表、状态、上下文、优先级、记账信息、tgid同一个进程的tgid是同一个 同一个进程的若干个线程是共用相同的内存资源和文件资源的这里的内存指针和文件描述符表其实是同一个但是每个线程都是独立在CPU上调度执行
进程是系统分配资源的基本单位线程是系统调度执行的基本单位
引入线程后就可以每个客户端分配一个线程来处理起到优化效果
1.3 为何线程更轻量
为什么线程比进程更轻量/为什么说线程创建和销毁的开销比进程更小 核心在于创建进程可能要包含多个线程这个过程中涉及到资源分配/资源释放创建线程相当于资源已经有了省去了资源分配/资源释放步骤了同一个进程包含N个线程这些线程之间是共用资源的只有创建第一个线程也是创建进程的时候去进行资源申请操作后续再创建线程都没有申请资源的过程了 1.4 问题
线程不能无限引入总的线程越多单位时间内要进行调度的次数也越多调度消耗的系统资源自然就更多了这个时候线程调度开销就会非常明显程序的性能可能不升反降线程安全问题多个线程之间可能产生冲突如果一个线程抛出异常并且没有很好的捕获处理好就会使得整个进程退出多线程编程值得关注的难点一个线程出现问题会影响到别的线程
2. 多线程代码
线程本身是操作系统提供的操作系统提供了api让我们操作线程JVM就对操作系统api进行了封装Java中提供了Thread类表示线程
2.1 继承Thread重写run public void run()run只是描述了线程要干啥任务run不是start调用的是start创建出来的线程线程里被调用的 t.start();Thread类中自带的方法调用操作系统提供的“创建线程”api在内核中创建对应的PCB并且把PCB加入到链表中进一步的系统调度到这个线程之后就会执行上述run方法中的逻辑 像run这种方法只是定义好而不用去手动调用把这个方法的调用交给系统/其他的库/其他的框架别人调用这样的方法函数称为**“回调函数”**callback function package thread;class MyThread extends Thread {Overridepublic void run() {for (int i 0; i 5; i) {System.out.println(hello thread);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class Demo1 {public static void main(String[] args) {Thread t new MyThread();t.start();for (int i 0; i 5; i) {System.out.println(hello main);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}上述代码中有两个线程
t 线程main方法所在的线程主线程JVM进程启动的时候自己创建的线程 JDK中包含了jconsole工具通过这个工具可以更直观地看到内部进程的情况里面除了main与t线程剩下的线程都是JVM帮我们做的一些其他工作有的是负责垃圾回收的有的是负责记录调试信息的 Thread.sleep(1000);让线程主动进入“阻塞状态”主动放弃去CPU上执行时间到了之后线程才会接触阻塞状态重新被调度到CPU上执行加上sleep就让CPU消耗的资源大幅度降低了不加入sleep消耗CPU资源将会特别大while循环太快了
未来实际开发中如果服务器程序消耗CPU的资源超出预期如何排查
先确认是哪个线程消耗的CPU比较高未来会涉及到到第三方工具可以看到每个线程的CPU的消耗情况确定了之后进一步排查线程中是否有类似的“非常快速”的循环确认清楚这里的循环是否应该这么快如果应该说明需要升级更好的CPU如果不应该说明需要在循环中引入一些等待操作不一定是sleep
上述代码补充说明
每秒钟打印一次每一秒打印的时候可能是main在前面也可能是thread在前面多个线程的调度顺序是无序的在操作系统内部称为**“抢占式执行”**任何一个线程在执行到任何一个代码的过程中都可能被其他线程抢占掉它的CPU资源于是CPU就给别的线程执行了这样的抢占式执行充满了随机性正是这样的随机性使多线程程序的执行效果也会难以预测甚至可能会引入bug主流的系统Linux、Windows都是属于这种实现方式也有一些小众的系统实时操作系统通过“协商式”进行调度虽然牺牲了很多功能换来了调度的实时性
2.2 通过实现Runnable接口创建线程 Runnable的作用是描述了一个“任务”这个任务和具体的执行机制无关通过线程的方式执行还是通过其他的方式执行run就是要执行的任务内容本身了 引入Runnable就是为了解耦合未来如果要更换其他的方式来执行这些任务改动成本比较低把任务内容和线程这个概念给拆分开了这样的任务就可以给其他的地方来执行
package thread;
class MyRunnable implements Runnable {Overridepublic void run() {for (int i 0; i 5; i) {System.out.println(hello thread);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class Demo2 {public static void main(String[] args) {Thread t new Thread(new MyRunnable());t.start();for (int i 0; i 5; i) {System.out.println(hello main);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}2.3 针对2.1的变形使用匿名内部类
package thread;public class Demo3 {public static void main(String[] args) {Thread t new Thread() {Overridepublic void run() {for (int i 0; i 5; i) {System.out.println(hello thread);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};t.start();for (int i 0; i 5; i) {System.out.println(hello main);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}此处的new Thread()
创建了一个Thread的子类不知道啥名字匿名同时创建了一个该子类的实例对于匿名内部类来说只能创建这一个实例之后再也拿不到这个匿名内部类了此处的子类内部重写了父类的run方法
2.4 针对Runnable创建匿名内部类
package thread;public class Demo4 {public static void main(String[] args) {Thread t new Thread(new Runnable() {Overridepublic void run() {for (int i 0; i 5; i) {System.out.println(hello thread);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});t.start();for (int i 0; i 5; i) {System.out.println(hello main);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}此处匿名内部类只是针对Runnable和Thread没有关系只是把Runnable的实例作为参数传入到了Thread的构造方法中
创建新的类实现Runnable但是类的名字是匿名的创建了这个新类的实例一次性重写run方法
2.5 使用lambda表达式
lambda本质上就是针对匿名内部类的平替
package thread;public class Demo5 {public static void main(String[] args) {Thread t new Thread(() -{for (int i 0; i 5; i) {System.out.println(hello thread);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();for (int i 0; i 5; i) {System.out.println(hello main);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}