• Java 多线程学习笔记


    概念

    进程

    • 正在运行的程序,是系统进行资源分配和调用的独立单位
    • 每一个进程都有它自己的内存空间和系统资源,一个进程包括由操作系统分配的内存空间,包含一个或多个线程
    • 一个进程一直运行,直到所有的非守护线程都结束运行后才能结束

    线程

    • 线程是进程中的单个顺序控制流,是一条执行路径
    • 一个进程如果只有一条执行路径,则称为「单线程程序」
    • 一个进程如果有多条执行路径,则称为「多线程程序」
    • 一个线程不能独立的存在,它必须是进程的一部分
    • 线程是 CPU 调度的最小单位

    Java 中对于线程的描述是 Thread,其中,封装了线程的相关信息,最重要的有需要执行的任务的信息;

    线程的交互

    互斥和同步:

    • 互斥是当一个线程正在运行时,其他的线程只能等待,当完成后就可以运行了。
    • 同步是两个或多个线程同时进行运行。

    线程的状态

    当你需要使用 Java 线程在多线程环境下进行编程时,理解 Java 的线程周期与线程的状态是非常重要的。

    线程分为五个阶段:

    • 创建(new)状态: 准备好了一个多线程的对象
    • 就绪(runnable)状态: 调用了 start() 方法, 它的状态变为 Runnable(可运行的)。控制权被给予线程调度程序来完成它的执行。 是否立即运行此线程或在运行之前将其保持在可运行线程池中,取决于线程调度程序的实现,等待 CPU 进行调度
    • 运行(running)状态: 执行 run() 方法,线程正在执行。线程调度程序从可运行线程池中选择一个线程,并将其状态更改为正在运行,然后 CPU 开始执行这个线程。
    • 阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用。
    • 终止(dead)状态: 一旦线程完成执行,它的状态就变成 Dead,线程销毁

    当需要新起一个线程来执行某个子任务时,就创建了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,在前面的 JVM 内存区域划分一篇博文中知道程序计数器、Java 栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。

    当线程进入就绪状态后,不代表立刻就能获取 CPU 执行时间,也许此时 CPU 正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。

    线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。

    当由于突然中断或者子任务执行完毕,线程就会被消亡。

    在有些教程上将 blocked、waiting、time waiting 统称为阻塞状态,这个也是可以的,只不过这里我想将线程的状态和 Java 中的方法调用联系起来,所以将 waiting 和time waiting 两个状态分离出来。

    注: sleep 和 wait 的区别:

    • sleep 是 Thread 类的方法,wait 是 Object 类中定义的方法.
    • Thread.sleep 不会导致锁行为的改变, 如果当前线程是拥有锁的, 那么 Thread.sleep 不会让线程释放锁.
    • Thread.sleep 和 Object.wait 都会暂停当前的线程. OS 会将执行时间分配给其它线程. 区别是, 调用 wait 后, 需要别的线程执行 notify/notifyAll 才能够重新获得 CPU 执行时间.

    参考:

    多线程的意义

    • 多进程的意义? 提高CPU的使用率
    • 多线程的意义? 提高应用程序的使用率

    Java 程序运行原理

    Java 命令会启动 Java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个「主线程」,然后主线程去调用某个类的 main 方法。

    所以,main 方法运行在主线程中。未采用多线程时,程序都是单线程的。

    扩展阅读

    线程实现方式

    线程创建方式——继承 Thread 类

    • 子类继承 Thread 类,子类中覆盖父类方法的 run 方法,将线程运行的代码放在 run 方法中;
    • 创建子类的实例,线程被创建;
    • 调用子类的实例的 start 方法,开启线程;
    public class Test {
        public static void main(String[] args)  {
            System.out.println("主线程ID:"+Thread.currentThread().getId());
            MyThread thread1 = new MyThread("thread1");
            thread1.start();
            MyThread thread2 = new MyThread("thread2");
            thread2.run();
        }
    }
    
    class MyThread extends Thread{
        private String name;
    
        public MyThread(String name){
            this.name = name;
        }
    
        @Override
        public void run() {
            System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
        }
    }
    

    执行结果:

    主线程ID:1
    name:thread2 子线程ID:1
    name:thread1 子线程ID:10
    

    同个线程的 start 方法重复调用的话,会出现 java.lang.IllegalThreadStateException 异常

    线程创建方式——实现 Runnable 接口

    • 子类实现 Runnable 接口,覆盖接口中的 run 方法;
    • 通过 Thread 类创建线程,并将实现了 Runnable 接口的子类对象作为 Thread 类的参数;
    • 通过 Thread 类的实例对象调用 start 方法,开启线程;
    public class Test {
        public static void main(String[] args)  {
            System.out.println("主线程ID:"+Thread.currentThread().getId());
            MyRunnable runnable1 = new MyRunnable("thread1");
            Thread thread1 = new Thread(runnable1);
            thread1.start();
            MyRunnable runnable2 = new MyRunnable("thread2");
            Thread thread2 = new Thread(runnable2);
            thread2.run();
        }
    }
    
    class MyRunnable implements Runnable{
        private String name;
    
        public MyRunnable(String name){
            this.name = name;
        }
    
        @Override
        public void run() {
            System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
        }
    }
    

    执行结果:

    主线程ID:1
    name:thread2 子线程ID:1
    name:thread1 子线程ID:10
    

    不管是扩展 Thread 类还是实现 Runnable 接口来实现多线程,最终还是通过 Thread 的对象的API 来控制线程的,熟悉 Thread 类的 API 是进行多线程编程的基础。

    在 Java 中,这 2 种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承 Thread 类的话,可能比实现 Runnable 接口看起来更加简洁,但是由于 Java 只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现 Runnable 接口。

    线程创建方式——实现 Callable 接口

    还有一种是实现 Callable 接口,并与 Future、线程池结合使用

    Thread 常用方法

    • getName() 获取当前线程的名称 Thread.currenthrad().getName
    继承Thread类,getName()获取当前线程名称。
    实现Runnable接口,Thread.currentThread().getName();获取当前线程名称。
    
    • Thread.join() 让其他线程等待当前线程执行完之后再执行,比如,当前线程在另外一个线程的 run 方法中,如果不加 join 方法,那么,当前线程可能未执行完毕,其他线程就会往下执行了;
    • Thread.sleep(1000) 休眠 1 秒;
    • Thread.yield(); 让出处理器时间,「线程们」去竞争吧
    • volatile 关键字,保证了线程可以正确的读取其他线程写入的值,如下示例:

    FAQ

    启动一个线程是 run() 还是 start()?它们的区别?

    start() 启动一个线程;

    • run 方法封装了被线程执行的代码,直接调用仅仅是普通方法的调用;
    • start 启动线程,并有 JVM 自动调用 run 方法;

    Thread和Runnable的区别

    如果一个类继承 Thread,则不适合资源共享。但是如果实现了 Runable 接口的话,则很容易的实现资源共享。

    总结,实现 Runnable 接口比继承 Thread 类所具有的优势:

    1. 适合多个相同的程序代码的线程去处理同一个资源
    2. 可以避免 Java 中的单继承的限制
    3. 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
    4. 线程池只能放入实现 Runablecallable 类线程,不能直接放入继承 Thread 的类

    提醒一下大家:main 方法其实也是一个线程。在 Java 中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到 CPU 的资源。

    在 Java 中,每次程序运行至少启动 2 个线程。一个是 main 线程,一个是垃圾收集线程。因为每当使用 Java 命令执行一个类的时候,实际上都会启动一个 JVM,每一个 JVM 实际就是在操作系统中启动了一个进程。

    如何正确的停止 Java 中的线程

    不要使用 stop 方法,因为不清楚哪些工作已经做了、哪些还没做、清理工作还没做……戛然而止的现象!

    正确的做法:设置退出旗标

    使用退出标志停止线程执行的方式的好处在于:

    1. 可以使线程执行完对应的操作后,因不符合继续执行的条件而停止
    2. 我们可以做一些线程执行结束后的清理工作
    3. 使线程的结束执行看起来是有次序的,而非戛然而止

    interrupt 方法不是停止线程的正确方法,原因是 interrupt() 方法初衷不是用于停止线程。interrupt() 方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被 Object.wait, Thread.joinThread.sleep 三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态,然后该线程还是继续运行的

    参考

  • 相关阅读:
    Pentaho BIServer Community Edtion 6.1 使用教程 第三篇 发布和调度Kettle(Data Integration) 脚本 Job & Trans
    Pentaho BIServer Community Edtion 6.1 使用教程 第二篇 迁移元数据 [HSQLDB TO MySQL]
    Pentaho BIServer Community Edtion 6.1 使用教程 第一篇 软件安装
    C调用约定__cdecl、__stdcall、__fastcall、__pascal分析
    django环境搭建和学习
    Nagios学习笔记
    MFC下的DLL编程学习
    从零开始学区块链(4)
    从零开始学区块链(3)
    从零开始学习区块链(2)
  • 原文地址:https://www.cnblogs.com/michael-xiang/p/10759789.html
Copyright © 2020-2023  润新知