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

简介: 认识线程、了解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编辑

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

                      相关文章
                      |
                      1月前
                      |
                      存储 消息中间件 资源调度
                      C++ 多线程之初识多线程
                      这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
                      48 1
                      C++ 多线程之初识多线程
                      |
                      30天前
                      |
                      Java 开发者
                      在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
                      【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
                      20 3
                      |
                      30天前
                      |
                      Java 开发者
                      在Java多线程编程中,选择合适的线程创建方法至关重要
                      【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
                      19 2
                      |
                      30天前
                      |
                      Java
                      Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
                      【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
                      30 2
                      |
                      30天前
                      |
                      Java 开发者
                      Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
                      【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
                      34 1
                      |
                      30天前
                      |
                      安全 Java 开发者
                      Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
                      本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
                      38 1
                      |
                      30天前
                      |
                      Java
                      在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
                      在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
                      25 1
                      |
                      2月前
                      |
                      数据采集 负载均衡 安全
                      LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
                      本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
                      LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
                      |
                      1月前
                      |
                      存储 前端开发 C++
                      C++ 多线程之带返回值的线程处理函数
                      这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
                      48 6
                      |
                      1月前
                      |
                      存储 运维 NoSQL
                      Redis为什么最开始被设计成单线程而不是多线程
                      总之,Redis采用单线程设计是基于对系统特性的深刻洞察和权衡的结果。这种设计不仅保持了Redis的高性能,还确保了其代码的简洁性、可维护性以及部署的便捷性,使之成为众多应用场景下的首选数据存储解决方案。
                      41 1