• java EE 多线程(一)


    • 本篇大概详解思路如下思维导图:

    目录

    在java程序中创建线程

    start()和run()的区别

    start()方法为啥不能被重复调用

    创建线程的7种方式

    多线程提高速度

    多线程的使用场景


    在java程序中创建线程

    我们知道一个进程包括多个线程(pcb---每个线程对应一块pcb,一个进程包括多个pcb),多个线程之间是并发执行的,其实当我们学第一个hello,world程序这个在运行的程序就是一个java进程

    创建一个java进程会创建很多进程(与JVM相关的....),同时也会创建一个主线程 -- main线程(这与main方法不一样)

    创建线程都与Thread类有关系,我们第一步就是创建它的实例

    创建一个新线程

    1. //自定义创建MyThread类 继承 Thread 类
    2. public static class MyThread extends Thread{
    3. @Override
    4. public void run() {//为线程准备任务 --- 线程接下来要执行的任务
    5. //此时并没有创建线程
    6. System.out.println("我是Thread....");
    7. }
    8. }
    9. public static void main(String[] args) {
    10. Thread t = new MyThread();//创建 线程实例对象 t
    11. //创建线程 将新建状态NEW设置为就绪状态,等待CPU时间片
    12. //(系统安排一个时间调用Thread.run方法)来调用run方法
    13. t.start();
    14. System.out.println("我是main");
    15. }

     通过打印结果我们就可以发现打印结果的顺序与代码的执行顺序是不同的. 所以,CPU在进行调度时是以不确定的方式或者随机时间来执行run任务的.

    再次理解并发编程

     随着执行程序,JVM就开启了一个java进程,创建main(主线程)开始执行,随后调用t.start(),同时新线程也被创建(这里任务不是立即执行而是设置就绪状态要等待系统让Thread来执行run任务),这里主线程和Thread线程之间是取决于操作系统的调度(我们可以简单理解为随机调度),最后两个线程任务执行完毕,最终整个进程就结束.

    每个线程都是独立的执行流,两个执行流是并发执行的,执行顺序取决于操作系统的调度

    start()和run()的区别

    1. public static class MyThread extends Thread{
    2. @Override
    3. public void run() {
    4. while(true){
    5. System.out.println("我是Thread!!!");
    6. try {
    7. sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. }
    13. }
    14. public static void main(String[] args) {
    15. Thread t = new MyThread();
    16. t.start();
    17. //t.run();
    18. while(true){
    19. System.out.println("我是main!!!");
    20. try {
    21. Thread.sleep(1000);
    22. } catch (InterruptedException e) {
    23. e.printStackTrace();
    24. }
    25. }
    26. }
    • 从这个代码就会发现当你调用run方法时它并不会打印main而是一直打印Thread,这是因为并没有创建新的线程而只是调用了普通的方法run
    • 当调用start()方法时你会发现main和Thread交替打印,原因就是调用start()方法是创建一个新的线程,两个线程之间(主线程和Thread线程)之间是并发执行的
    • 调用start()方法才是真正的创建一个新的线程,而调用run()只是调用主程序的一个方法并没有创建新的线程
    • 因为一个线程只能被创建一次,所以start()方法不能被重复调用(如果重复调用会抛出异常),而run方法是一个普通的方法可以重复被调用
    • 调用start()方法不是立即被执行而是将新建状态设置为就绪状态,调用run方法是立即执行任务

    start()方法为啥不能被重复调用

    通过源码:

    因为一个线程只能被创建一次,所以start()不能被重复调用,线程是不可逆的,当第一次调用start()方法时他会将新建状态NEW设置为就绪状态,当再次调用start()方法是就会抛出异常.


    创建线程的7种方式

    • 通过自定义MyThread类来继承Thread类
    1. public static class MyThread extends Thread{
    2. @Override
    3. public void run() {//准备任务--这里只是要执行的任务准备好
    4. while(true){
    5. System.out.println("我是Thread!!!");
    6. try {
    7. sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. }
    13. }
    14. public static void main(String[] args) {
    15. Thread t = new MyThread();//创建Thread实例t
    16. t.start();//真正的创建线程
    17. while(true){
    18. System.out.println("我是main!!!");
    19. try {
    20. Thread.sleep(1000);
    21. } catch (InterruptedException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. }
    • 实现Runnable接口
    1. public static class MyRunnable implements Runnable{
    2. @Override
    3. public void run() {
    4. System.out.println("我是Thread!!!");
    5. }
    6. }
    7. public static void main(String[] args) {
    8. Runnable r = new MyRunnable();//创建Runnable接口的实例
    9. Thread t = new Thread(r);//将Runnable接口对象传进Thread中
    10. t.start();
    11. System.out.println("我是main!!!");
    12. }

    这里因为Thread类有一个构造方法就可以传Runnable接口对象 Thread(Runnable target)

     通过源码这里也可以看到Thread类实现了Runnable接口

    • 通过Thread匿名内部类的形式
    1. public static void main(String[] args) {
    2. Thread t = new Thread(){
    3. @Override
    4. public void run() {//准备任务
    5. System.out.println("我是Thread!!!");
    6. }
    7. };
    8. t.start();//创建线程
    9. System.out.println("我是main!!!");
    10. }
    • 通过Runnable匿名内部类的形式
    1. public static void main(String[] args) {
    2. Thread t = new Thread(new Runnable() {
    3. @Override
    4. public void run() {
    5. System.out.println("我是Thread!!!!");
    6. }
    7. });
    8. t.start();
    9. System.out.println("我是main!!!");
    10. }
    • 通过lambda表达式(推荐做法)
    1. public static void main(String[] args) {
    2. Thread t = new Thread(()->{
    3. System.out.println("我是Thread!!!");
    4. });
    5. t.start();
    6. System.out.println("我是main!!!");
    7. }

    为啥使用线程???

    我们知道创建一个进程首先创建一块pcb,然后为进程分配系统资源,最后将pcb加到双向链表中,因为这个位进程分配系统资源造成CPU速率下降,所以引出线程,线程只需要创建pcb,然后将pcb加到链表中,因为多个线程之间共享同一个进程的资源

    单核CPU已经发挥到极致,要想速度更快就要充分利用CPU多核,就要进行并发编程--多个线程并发执行,又因为在某些场景下需要等待IO,为了让在等待IO时间内多做一些其他事情,就需要多个线程之间并发执行.

    使用线程坏处???

    当线程数过多是一个线程崩溃可能会导致整个进程崩溃,还有可能会导致线程安全问题,上下文切换,死锁问题.

    多线程提高速度

    我们来演示一下单个线程,和多个线程的速度比较

    1. public static final long COUNT = 20_0000_0000;
    2. public static void serial(){
    3. long start = System.currentTimeMillis();
    4. long a = 0;
    5. for(int i =0;i
    6. a++;
    7. }
    8. a = 0;
    9. for(int i =0;i
    10. a++;
    11. }
    12. long end = System.currentTimeMillis();
    13. System.out.println("单线程所消耗的时间" + (end - start) + "ms");
    14. }
    15. public static void currency(){
    16. long start = System.currentTimeMillis();
    17. Thread t1 = new Thread(()->{;
    18. long a = 0;
    19. for(int i =0;i
    20. a++;
    21. }
    22. });
    23. Thread t2 = new Thread(()->{
    24. long a = 0;
    25. for(int i =0;i
    26. a++;
    27. }
    28. });
    29. t1.start();
    30. t2.start();
    31. try {
    32. t1.join();//等待t1线程结束任务在执行下一个
    33. t2.join();//等待t2线程结束任务在执行,如果没有任务就返回/退出
    34. } catch (InterruptedException e) {
    35. e.printStackTrace();
    36. }
    37. long end = System.currentTimeMillis();
    38. System.out.println("多线程所消耗的时间" + (end - start) + "ms");
    39. }
    40. public static void main(String[] args) {
    41. serial();
    42. currency();
    43. }

    通过代码演示多个线程之间执行比单个线程之间执行速度快一些,但是并没有精准的两个线程之间就等一2*一个线程的速度,因为会有一些会有调度以及创建自身线程的开销.

    多线程的使用场景

    1.CPU密集型场景

    使用多线程,可以充分利用CPU多核资源,提高效率

    2.IO密集型场景

    由于IO操作不消耗CPU就能快速完成读写操作,在这段等待IO(读写硬盘,读写网卡...)操作的时间,使用多线程让CPU能够做一些其他的工作

     

  • 相关阅读:
    LeetCode刷题系列 -- 24. 两两交换链表中的节点
    解决SpringBoot3整合Druid的兼容性问题
    P02 Look And Feel
    set和multiset容器
    B轮融资背后:未势能源在万亿“长坡”上,铺出三重“厚雪”
    写给Java应用开发看的Elasticsearch调优手册
    Python爬虫之入门保姆级教程
    拖拽式万能DIY小程序源码系统 5分钟创建一个小程序,操作简单 带完整的部署搭建教程
    YOLOv5结合GradCAM热力图可视化
    android7.1 系统ota升级与升级失败解决方法
  • 原文地址:https://blog.csdn.net/m0_61210742/article/details/126005402