结合多本著作和个人开发经验,整理Java多线程入门手册(1)

上一篇 / 下一篇  2023-02-03 10:10:16

  前言
  前段时间推出的Java8新特性文章收到大家广泛关注和好评,非常感谢各位支持,这段时间苦思冥想,决定输出一波Java多线程技能点,希望可以在大家的工作和面试中有所帮助!本篇文章为多线程系列第一章,主要讲解一下几点:
  ·多线程好处和应用场景
  · 多线程的相关概念和术语
  · Java线程创建方式
  · Thread类详解,线程的常用方法
  · 线程5种状态和6种状态,两种版本解释
  · 线程状态之间转换
  Java设计者写过一个很有影响力的白皮书,用来解释设计的初衷,并发布了一个简短的摘要,分为11个术语:
  · 简单性
  · 面向对象
  · 分布式
  · 健壮性
  · 安全性
  · 体系结构中立
  · 可移植性
  · 解释型
  · 高性能
  · 多线程
  · 动态性
  其中多线程就是本次要接触的,白皮书中对多线程的解释:
  多线程可以带来更好的交互响应和实时行为。
  如今,我们非常关注并发性,因为摩尔定律行将完结。我们不再追求更快的处理器,而是着眼于获得更多的处理器,而且要让它们一直保持工作。不过,可以看到,大多数编程语言对于这个问题并没有显示出足够的重视。Java在当时很超前。它是第一个支持并发程序设计的主流语言。从白皮书中可以看到,它的出发点稍有些不同。当时,多核处理器还很神秘,而Web编程才刚刚起步,处理器要花很长时间等待服务器响应,需要并发程序设计来确保用户界面不会"冻住"。并发程序设计绝非易事,不过Java在这方面表现很出色,可以很好地管理这个工作。
  在操作系统中有多任务【multitasking】,在同一刻运行多个程序【应用】的能力。例如,在听音乐的同时可以边打游戏,边写代码。如今我们的电脑大多都是多核CPU,但是,并发执行的进程【正在执行的应用】数目并不是由CPU数目制约的。操作系统将CPU的时间片分配给每一个进程,给人并行处理的感觉。
  相关概念
  程序【program】:为了完成特定任务,用某种语言编写的一组指令的集合。程序就是一堆代码,一组数据和指令集,是一个静态的概念。就说我们程序员写的那玩意。比如:安装在电脑或者手机上的各种软件,今日头条、抖音、懂车帝等,如果一个程序支持多线程,这个程序就是一个多线程程序
  进程【Process】:是程序的一次执行过程或者说是正在运行的程序,是一个动态概念,进程存在生命周期,也就是说程序随着程序的终止而销毁
  线程【Thread】:线程是进程中的实际运作的单位,是进程的一条流水线,是程序的实际执行者,是最小的执行单位。通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程。线程是CPU调度和执行的最小单位
  CPU时间片:时间片即CPU分配给各个程序的时间,每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,使各个程序从表面上看是同时进行的,如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。而不会造成CPU资源浪费
  并行【parallel】:多个任务同时进行,并行必须有多核才能实现,否则只能是并发,比如:多名学生有问题,同时有多名老师可以辅导解决。
  串行【serial】:一个程序处理完当前进程,按照顺序接着处理下一个进程,一个接着一个进行,比如:多名学生有问题,只有一名老师,需要挨个解决。
  并发【concurrency】:同一个对象被多个线程同时操作。(这是一种假并行。即一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉),比如:多名学生有问题,只有一个老师,他一会处理A同学,一会处理B同学,一会处理C同学,频繁切换,看起来好似在同时处理学生问题。
  多线程意义
  实际应用中,多线程非常有用,例如,QQ音乐就是一个多线程程序,我们可以一边听音乐,一般下载音乐,还可以同时播放MV等非常方便。一个Web服务器通过多线程同时处理多个请求,比如Tomcat就是多线程的。
  注意:程序会因为引入多线程而变的复杂,多线程同时会带来一些问题,需要我们解决
  多线程应用场景
  · 程序需要同时执行两个或多个任务。
  · 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  · 需要一些后台运行的程序时。
  多线程多数在浏览器、Web服务器、数据库、各种专用服务器【如游戏服务器】、分布式计算等场景出现。
  在使用Java编写后台服务时,如果遇到并发较高、需要后台任务、需要长时间处理大数据等情况都可以创建线程单独的线程处理这些事项,多线程的目的就在于提高处理速度,减少用户等待时间
  · 后台记录日志,创建额外线程来记录日志
  · 大量用户请求,创建多个线程共同处理请求
  · 下载大文件,可以创建单独的线程,不影响正常的业务流畅度
  ......
  多线程创建方式
  线程创建有4种方式:
  方式1:继承Thread类
  方式2:实现Runnable接口
  方式3:实现Callable接口
  方式4:使用线程池【这块后边单独说,它更像是管理线程的手段】
  继承Thread
  步骤:
  · 自定义类继承Thread类
  · 重写run方法,run方法就是线程的功能执行体
  · 创建线程对象,调用start方法启动线程
  · 启动线程之后,线程不一定会立即执行,需要得到CPU分配的时间片,也就是拿到CPU执行权限才会执行
  JDK源码中,Thread类定义实现了Runnable接口。
  所以知道重写的run方法从哪来的了吧!就是从Runnable接口中来的。
  需求:创建线程计算10以内的偶数。
  线程类:
  public class ThreadTest extends Thread{
      // run方法是 线程体,启动线程时会运行run()方法中的代码
      @Override
      public void run() {
          // 输出10以内偶数
          for (int i = 0; i < 10; i++) {
              if (i % 2 == 0){
                  System.out.println(i);
              }
          }
      }
  }
  测试类:
  测试类中输出了一句话:主线程。
  public class ThreadMain {
      public static void main(String[] args) {
          // 1、创建线程对象
          ThreadTest t1 = new ThreadTest();
          // 2、调用start方法启动线程
          t1.start();
          System.out.println("主线程");
      }
  }
  打印结果:
  实现Runnable接口
  步骤:
  ·自定义类实现Runnable接口
  · 实现run方法
  · 创建实现类对象
  · 创建Thread对象,在构造方法中传入实现类对象作为参数
  · 调用Thread对象的start方法启动线程
  同样的需求打印10以内的偶数
  实现类:
  public class RunnableImpl implements Runnable{
      @Override
      public void run() {
          // 输出10以内偶数
          for (int i = 0; i < 10; i++) {
              if (i % 2 == 0){
                  System.out.println(i);
              }
          }
      }
  }
  测试类:
  public class RunnableMain {
      public static void main(String[] args) {
          // 1、创建实现类对象
          RunnableImpl runnable = new RunnableImpl();
          // 2、创建线程对象,接收实现类,因为实现类中的run方法承载了线程的功能
          Thread t1 = new Thread(runnable);
          // 3、启动线程
          t1.start();
          // 主线程
          System.out.println("主线程");
      }
  }
  Callable接口
  FutureTask类:
  RunnableFuture接口:
  步骤:
  ·新建类实现Callable接口,并指定泛型类型,类型就是线程计算之后的结果的类型
  · 实现call方法,call方法跟run方法类似,不过该方法有返回值和异常抛出,都是来封装线程功能体的
  · 在测试类中创建实现类对象,并且创建 FutureTask 对象将实现类对象当做构造方法参数传入
  · 创建Thread线程对象,将 FutureTask 对象当做构造方法参数传入,并调用start方法启动线程
  可以通过 FutureTask 对象的get方法获取线程的运算结果
  案例:还是计算10以内的偶数,这一次将计算结果返回,因为有多个数据所以返回数据用集合存储,则Callable接口的泛型类型应该是集合
  实现类:
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.Callable;
  // 1、实现Callable,指明泛型类型
  public class CallableImpl implements Callable<List<Integer>> {
      // 2、线程返回Integer类型数据,抛出异常
      @Override
      public List<Integer> call() throws Exception {
          List<Integer> list = new ArrayList<>();
          for (int i = 0; i < 10; i++) {
              if (i % 2 == 0){
                  // 3、偶数存储到集合中
                  list.add(i);
              }
          }
          // 4、返回集合
          return list;
      }
  }
  测试类:
  import java.util.List;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.FutureTask;
  public class CallableMain {
      public static void main(String[] args) {
          // 1、创建Callable实现类对象
          CallableImpl callable = new CallableImpl();
          // 2、创建 FutureTask对象传入 callable
          // FutureTask 实现 了 RunnableFuture,RunnableFuture实现了Runnable接口和Future接口
          FutureTask<List<Integer>> task = new FutureTask<>(callable);
          // 3、将 task 传入线程对象
          Thread t1 = new Thread(task);
          // 4、启动线程
          t1.start();
          // 5、获取线程返回数据
          try {
              List<Integer> list = task.get();
              System.out.println(list);
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  }


TAG: Java java 多线程

 

评分:0

我来说两句

Open Toolbar