【多线程】初识线程,基础了解

简介: 认识线程、了解Thread类及常见方法、线程状态

目录

认识线程

   概念

       什么是线程?

       为啥要有线程

       进程和线程的区别

       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("这是线程运行代码");
            }
        }

        image.gif

               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("这是线程运行代码");
            }
        }

        image.gif

        使用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();
                }
            }

            image.gif

            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();
                }
            }

            image.gif

                  4.Lmabda表达式

            public class lambda {
                public static void main(String[] args) {
                    Thread thread = new Thread(() -> {
                        System.out.println("这是线程执行代码");
                    });
                    thread.start();
                }
            }

            image.gif

            用Lambda表达式实现的接口必须是函数式接口(接口中只有一个没有实现的方法)

            使用多线程编程主要是为了充分利用CPU资源,提升程序运行效率;

            但并不是所有场景使用多线程都可以提高效率

            Thread 类及常见方法

            Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。

            每个执行流,也需要有一个对象来描述,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

               Thread 的常见构造方法

            image.gif编辑

                Thread 的几个常见属性

            属性

            获取方法
            ID getId()
            名称 getName()
            状态 getState()
            优先级 getPriority()
            是否后台线程 isDaemon()
            是否存活 isAlive()
            是否被中断 isInterrupted()
              • ID 是线程的唯一标识,不同线程不会重复
              • 名称是各种调试工具用到
              • 状态表示线程当前所处的一个情况
              • 优先级高的线程理论上来说更容易被调度到
              • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
              • 是否存活,即简单的理解,为 run 方法是否运行结束了
              • 线程的中断问题

                 启动一个线程-start()

              之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。

               覆写 run 方法是提供给线程要做的事情的指令清单

               线程对象可以认为是把 李四、王五叫过来了

               而调用 start() 方法,就是喊一声:行动起来!,线程才真正独立去执行了。

              调用 start 方法, 才真的在操作系统的底层创建出一个线程.

                 中断一个线程

              李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉及到我们的停止线程的方式了。

              目前常见的有以下两种方式:

                1. 通过共享的标记来进行沟通
                2. 调用 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();
                    }
                }

                image.gif

                image.gif编辑

                thread 收到通知的方式有两种:

                1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通

                知,清除中断标志

                  • 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择
                    忽略这个异常, 也可以跳出循环结束线程.

                  2. 否则,只是内部的一个中断标志被设置,thread 可以通过

                    • Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
                    • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

                       等待一个线程-join()

                    有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。

                    image.gif编辑

                        获取当前线程引用

                    方法

                    说明

                    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);
                            }
                        }
                    }

                    image.gif

                      • NEW: 安排了工作, 还未开始行动
                      • RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
                      • BLOCKED: 这几个都表示排队等着其他事情
                      • WAITING: 这几个都表示排队等着其他事情
                      • TIMED_WAITING: 这几个都表示排队等着其他事情
                      • TERMINATED: 工作完成了.

                      image.gif编辑

                      这里只是线程的部分知识,剩下的知识总结我会后面继续发布,期待大家关注

                      相关文章
                      |
                      2月前
                      |
                      存储 监控 Java
                      Java多线程优化:提高线程池性能的技巧与实践
                      Java多线程优化:提高线程池性能的技巧与实践
                      64 1
                      |
                      6天前
                      |
                      数据采集 负载均衡 安全
                      LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
                      本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
                      LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
                      |
                      14天前
                      |
                      Java Spring
                      spring多线程实现+合理设置最大线程数和核心线程数
                      本文介绍了手动设置线程池时的最大线程数和核心线程数配置方法,建议根据CPU核数及程序类型(CPU密集型或IO密集型)来合理设定。对于IO密集型,核心线程数设为CPU核数的两倍;CPU密集型则设为CPU核数加一。此外,还讨论了`maxPoolSize`、`keepAliveTime`、`allowCoreThreadTimeout`和`queueCapacity`等参数的设置策略,以确保线程池高效稳定运行。
                      77 10
                      spring多线程实现+合理设置最大线程数和核心线程数
                      |
                      22天前
                      |
                      Java 数据库 Android开发
                      一个Android App最少有几个线程?实现多线程的方式有哪些?
                      本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
                      38 15
                      一个Android App最少有几个线程?实现多线程的方式有哪些?
                      |
                      8天前
                      |
                      Python
                      5-5|python开启多线程入口必须在main,从python线程(而不是main线程)启动pyQt线程有什么坏处?...
                      5-5|python开启多线程入口必须在main,从python线程(而不是main线程)启动pyQt线程有什么坏处?...
                      |
                      24天前
                      |
                      Java 数据库 Android开发
                      一个Android App最少有几个线程?实现多线程的方式有哪些?
                      本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
                      38 10
                      |
                      5天前
                      |
                      NoSQL 网络协议 Unix
                      1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
                      1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
                      16 1
                      |
                      6天前
                      |
                      Java
                      COMATE插件实现使用线程池高级并发模型简化多线程编程
                      本文介绍了COMATE插件的使用,该插件通过线程池实现高级并发模型,简化了多线程编程的过程,并提供了生成结果和代码参考。
                      |
                      1月前
                      |
                      存储 Ubuntu Linux
                      C语言 多线程编程(1) 初识线程和条件变量
                      本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。