前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >认识多线程就看这一篇吧

认识多线程就看这一篇吧

作者头像
东边的大西瓜
发布2022-05-05 11:21:03
2140
发布2022-05-05 11:21:03
举报
文章被收录于专栏:吃着西瓜学Java

前言

想必大家对电脑都不陌生,不管你的电脑是Windows系统还是Mac系统,现在都已经是多任务操作系统,多任务就是可以在同一时间运行多个程序的能力。多任务带给我们的最直观的体验,就是我们可以边听音乐边打字。 不过多任务操作系统不一定要有多CPU,让多个任务分时共享CPU就好了。比如Windows多任务处理采用的是虚拟机技术。而且要注意的是单核的多任务操作系统在宏观上(看起来)是并行的,微观上(程序运行时)是并发的。(有空聊聊多核、单核、多CPU?)

什么是线程?

在引入线程前我们先来了解下什么是进程,因为线程是进程中的一个实体,线程本身是不会独立存在的。

  • 进程:进程是代码在数据集合上的一次运行活动,是一个动态概念。它是系统进行资源分配和调度的基本单位,每个进程都拥有自己的一整套变量(地址空间)。进程的五种基本状态:初始态、执行态、等待状态、就绪状态、终止状态。
  • 线程:线程是进程的一个执行路径,一个进程至少有一个线程,进程中的多个线程共享进程的资源。线程是CPU分配的基本单位。

这里我们抽象的来理解下,我们可以将进程比作是一场正在进行的演唱会,而线程就是歌手、吉他手、鼓手等表演人员,台下的观众就是资源。

  • 【一个进程包含至少一个进程】:当演奏一首歌时,歌手在唱歌,吉他手在弹吉他,鼓手在敲鼓。当然歌手可以选择清唱。
  • 【多个线程共享进程的资源】:一场演唱会的观众是固定的,因此表演人员们所能获得到的关注度是一定的,我们这里假定每个观众都只能关注一个自己喜欢的,比如歌手或者吉他手。

总结:进程是资源分配的基本单位,线程是CPU分配的基本单位。(也可以说是最小单位)

当然我们可以从更专业的角度来理解进程和线程之间的关系:(以下内容参考自《Java并发编程之美》、《深入理解Java虚拟机》)

在Java中,当我们启动main函数时,其实就是启动了一个JVM进程,而main函数所在的线程就是这个进程中的一个进程,也称主线程。

从上图我们可以看到,一个进程中可以有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程都有自己的程序计数器和栈区域。

  • 程序计数器:下一条指令地址。作用就是记录当前线程让出CPU时的执行地址,等到再次分配到CPU时间片时线程基于可以从自己私有的计数器指定地址继续执行。简单一句话,就是记录之前程序执行到哪里了。(可以理解为轮盘->)
  • 栈:每个线程都有自己的栈资源,用于存储该线程的局部变量,这些局部变量是该线程私有的,还可以存放线程的调用栈帧。
  • 堆:是一个进程中最大的一块内存,堆是被进程中的所有线程共享的,是进程创建时分配的。我们在讲类和对象时提到过,几乎所有的对象实例和数组都是在堆中分配内存的。
  • 方法区:方法区和Java堆一样,也是各个线程共享变量的内存区域,它用于存放已被JVM加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是他也有一个别名叫做“非堆”(Non-Heap),目的是与Java堆区分开来。

Java线程的创建方式

常见的Java线程的3种创建方式:继承Thread类、实现Runnable接口、和Callable实现有返回值的线程。

继承Thread类

代码语言:javascript
复制
public class Algorithm {
  static int i=0;
  static class MyThread extends Thread {
    private String name;
    public MyThread(){};
    public MyThread(String name){
        this.name = name;
    }

    @Override
    public void run() {
        for(int k=0;k<=100;k++) {
            System.out.println(name+" " +k);
        }

    }
}

public static void main(String[] args) {
    MyThread thread1 = new MyThread("线程1");
    MyThread thread2 = new MyThread("线程2");
        thread1.start();
        thread2.start();
    }

} 

从下图的执行结果中可以看出,线程的调度和先后是没关系的,而且线程1、2是交替执行的。

实现Runnable接口

代码语言:javascript
复制
public class Run {
  static class MyRunnable implements Runnable{
      @Override
      public void run() {
          System.out.println("Hello");
      }
  }

  public static void main(String[] args) {
      MyRunnable runnable = new MyRunnable();
      new Thread(runnable).start();
  }
}

从上述代码我们可以看出,Runnable的实现依赖于Thread。当一个实现了Runnable的线程实例给Thread后,Threa会调用Runnable的run方法。

实现Callable接口

代码语言:javascript
复制
public class Call {

  static class MyCallable implements Callable<String>{

      @Override
      public String call() throws Exception {
          return "Hello";
      }
  }

  public static void main(String[] args) {
      FutureTask<String> futureTesk = new FutureTask(new MyCallable());
      new Thread(futureTesk).start();
      try{
          String result = futureTesk.get();
          System.out.println(result);

      }catch (ExecutionException | InterruptedException e){
          e.printStackTrace();
      }
  }
}

这是一个具有返回值的线程实现方式。

END

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 吃着西瓜学Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 什么是线程?
  • Java线程的创建方式
    • 继承Thread类
      • 实现Runnable接口
        • 实现Callable接口
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档