浅析 Java多线程

 更新时间:2020年09月26日 11:03:22   作者:城北有个混子  
这篇文章主要介绍了Java多线程的相关资料,帮助大家更好的理解和学习Java线程相关知识,感兴趣的朋友可以了解下

什么是进程

  当一个程序进入内存中运行起来它就变为一个进程。因此,进程就是一个处于运行状态的程序。同时进程具有独立功能,进程是操作系统进行资源分配和调度的独立单位。

什么是线程

  线程是进程的组成部分。通常情况下,一个进程可拥有多个线程,而一个线程只能拥有一个父进程。

  线程可以拥有自己的堆栈、自己的程序计数器及自己的局部变量,但是线程不能拥有系统资源,它与其父进程的其他线程共享进程中的全部资源,这其中包括进程的代码段、数据段、堆空间以及一些进程级的资源(例如,打开的文件等)。

  线程是进程的执行单元,是CPU调度和分派的基本单位,当进程被初始化之后,主线程就会被创建。同时如果有需要,还可以在程序执行过程中创建出其他线程,这些线程之间也是相互独立的,并且在同一进程中并发执行。因此一个进程中可以包含多个线程,但是至少要包含一个线程,即主线程。

Java中的线程

  Java 中使用Thread类表示一个线程。所有的线程对象都必须是Thread或其子类的对象。Thread 类中的 run 方法是该线程的执行代码。让我们来看一个实例:

public class Ticket extends Thread{
  // 重写run方法
  public void run() {
    for (int i = 0; i < 20; i++) {
      System.out.println(getName() + ": " + i);
    }
  }
}
public class TestThread {
  public static void main(String[] args) {
    // 1.创建线程
    Thread thread1 = new Ticket();
    Thread thread2 = new Ticket();
    
    // 2.启动线程
    thread1.start();
    thread2.start();
  }
}

运行结果如下:

 通过上面的代码和运行结果,我们可以得到:

线程运行的几个特点

  1.同一进程下不同线程的调度不由程序控制。线程的执行是抢占式的,运行的顺序和线程的启动顺序是无关的,当前运行的线程随时都可能被挂起,然后其他进程抢占运行。

  2.线程独享自己的堆栈程序计数器和局部变量。两个进程的局部变量互不干扰,各自的执行顺序也是互不干扰。

  3.两个线程并发执行。两个线程同时向前推进,并没有说执行完一个后再执行另一个。

start()方法和run()方法

  启动一个线程必须调用Thread 类的 start()方法,使该线程处于就绪状态,这样该线程就可以被处理器调度。

   run()方法是一个线程所关联的执行代码,无论是派生自 Thread类的线程类,还是实现Runnable接口的类,都必须实现run()方法,run()方法里是我们需要线程所执行的代码。

  实现多线程必须调用Thread 类的 start()方法来启动线程,使线程处于就绪状态随时供CPU调度。如果直接调用run()方法的话,只是调用了Thread类的一个普通方法,会立即执行该方法中的代码,并没有实现多线程技术。

Java中多线程的实现方法

  在Java中有三种方法实现多线程。

    第一种方法:使用Thread类或者使用一个派生自Thread 类的类构建一个线程。

    第二种方法:实现Runnable 接口来构建一个线程。(推荐使用)

    第三种方法:实现Callable 接口来构建一个线程。(有返回值)

第一种方法

  使用Thread类或者使用一个派生自Thread 类的类构建一个线程。

public class Ticket extends Thread{
  // 重写run方法
  public void run() {
    for (int i = 0; i < 20; i++) {
      System.out.println(getName() + ": " + i);
    }
  }
}
public class TestThread {
  public static void main(String[] args) {
    // 1.创建线程
    Thread thread1 = new Ticket();
    Thread thread2 = new Ticket();
    
    // 2.启动线程
    thread1.start();
    thread2.start();
  }
}

  看上面的代码,我们创建了一个Ticket类,它继承了Thread类,重写了Thread类的run方法。然后我们用Ticket类创建了两个线程,并且启动了它们。但我们不推荐使用这种方法,因为一个类继承了Thread类,那它就没有办法继承其他类了,这对较为复杂的程序开发是不利的。

第二种方法

  实现Runnable 接口来构建一个线程。

public class Ticket implements Runnable{
  // 重写run方法
  public void run() {
    for (int i = 0; i < 20; i++) {
      System.out.println(Thread.currentThread().getName() + ": " + i);
    }
  }
}
public class TestThread {
  public static void main(String[] args) {
    // 1.创建线程
    Ticket t1 = new Ticket();
    Ticket t2 = new Ticket();
    Thread thread1 = new Thread(t1, "买票1号");
    Thread thread2 = new Thread(t2, "买票2号");
    
    // 2.启动线程
    thread1.start();
    thread2.start();
  }
}

  我们创建了一个Ticket类,实现了Runnable接口,在该类中实现了run方法。在启动线程前,我们要创建一个线程对象,不同的是我们要将一个实现了Runnable接口的类的对象作为Thread类构造方法的参数传入,以构建线程对象。构造方法Thread的第二个参数用来指定该线程的名字,通过Thread.currentThread().getName()可获取当前线程的名字。

  在真实的项目开发中,推荐使用实现Runnable接口的方法进行多线程编程。因为这样既可以实现一个线程的功能,又可以更好地复用其他类的属性和方法。

第三种方法

  实现Callable 接口来构建一个线程。

public class TestThread {
  public static void main(String[] args) {  
    // 1.创建Callable的实例
    Callable<String> callable = new Callable<String>() {
      @Override
      public String call() throws Exception {
        Thread.sleep(7000);
        return "我结束了";
      }
    };
    
    // 2.通过FutureTask接口的实例包装Callable的实例
    FutureTask<String> futureTask = new FutureTask<String>(callable);
    
    // 3.创建线程并启动
    new Thread(futureTask).start();
    
    // 4.获得结果并打印
    try {
      System.out.println(futureTask.get());
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

  首先我们用匿名内部类创建了一个实现Callable接口的类的对象,然后通过FutureTask 的实例包装了Callable的实例,这样我们就可以通过一个Thread 对象在新线程中执行call()方法,同时又可以通过get方法获取到call()的返回值。然后创建线程并启动它,最后在线程执行完执行完call()方法后得到返回值并打印。

  我们来看一下Callable的源码:

public interface Callable<V> {
  /**
   * Computes a result, or throws an exception if unable to do so.
   *
   * @return computed result
   * @throws Exception if unable to compute a result
   */
  V call() throws Exception;
}

  从Callable 的定义可以看出,Callable接口是一个泛型接口,它定义的call()方法类似于Runnable 的run()方法,是线程所关联的执行代码。但是与run()方法不同的是,call()方法具有返回值,并且泛型接口的参数V指定了call()方法的返回值类型。同时,如果call()方法得不到返回值将会抛出一个异常,而在Runnable的run()方法中不能抛出异常。

如何获得call()方法的返回值

  通过Future接口来获取。Future接口定义了一组对 Runnable 或者Callable 任务的执行结果进行取消、查询、获取、设置的操作。其中get方法用于获取call()的返回值,它会发生阻塞,直到call()返回结果。

这样的线程调用与直接同步调用函数有什么差异

  在上面的例子中,通过future.get()获取 call()的返回值时,由于call方法中会 sleep 7s,所以在执行future.get()的时候主线程会被阻塞而什么都不做,等待call()执行完并得到返回值。但是这与直接调用函数获取返回值还是有本质区别的。

  因为call()方法是运行在其他线程里的,在这个过程中主线程并没有被阻塞,还是可以做其他事情的,除非执行future.get()去获取 call()的返回值时主线程才会被阻塞。所以当调用了Thread.start()方法启动 Callable 线程后主线程可以执行别的工作,当需要call()的返回值时再去调用future.get()获取,此时call()方法可能早已执行完毕,这样就可以既确保耗时操作在工作线程中完成而不阻挡主线程,又可以得到线程执行结果的返回值。而直接调用函数获取返回值是一个同步操作,该函数本身就是运行在主线程中,所以一旦函数中有耗时操作,必然会阻挡主线程。

以上就是浅析 Java多线程的详细内容,更多关于Java多线程的资料请关注脚本之家其它相关文章!

相关文章

  • 使用JDBC连接Mysql 8.0.11出现了各种错误的解决

    使用JDBC连接Mysql 8.0.11出现了各种错误的解决

    这篇文章主要介绍了使用JDBC连接Mysql 8.0.11出现了各种错误的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • Java中循环冗余校验(CRC32)的实现

    Java中循环冗余校验(CRC32)的实现

    CRC校验实用程序库在数据存储和数据通讯领域,为了保证数据的正确,就不得不采用检错的手段,下面这篇文章主要给大家介绍了关于Java中循环冗余校验(CRC32)实现的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-10-10
  • Java SpringBoot在RequestBody中高效的使用枚举参数原理案例详解

    Java SpringBoot在RequestBody中高效的使用枚举参数原理案例详解

    这篇文章主要介绍了Java SpringBoot在RequestBody中高效的使用枚举参数原理案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • Java service层获取HttpServletRequest工具类的方法

    Java service层获取HttpServletRequest工具类的方法

    今天小编就为大家分享一篇关于Java service层获取HttpServletRequest工具类的方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • java 方法重写与权限修饰符以及多态和抽象类详解概念和用法

    java 方法重写与权限修饰符以及多态和抽象类详解概念和用法

    重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写,权限修饰符用于控制被修饰变量、方法、类的可见范围,说明了面向对象的封装性,所以我们要适用他们尽可能的让权限降到最低,从而安全性提高
    2021-10-10
  • 聊聊Spring Cloud Cli 初体验

    聊聊Spring Cloud Cli 初体验

    这篇文章主要介绍了聊聊Spring Cloud Cli 初体验,SpringBoot CLI 是spring Boot项目的脚手架工具。非常具有实用价值,需要的朋友可以参考下
    2018-04-04
  • IDEA设置Maven自动编译model的实现方法

    IDEA设置Maven自动编译model的实现方法

    本文主要介绍了IDEA设置Maven自动编译model的实现方法, maven结构的项目,我们在每次修改代码后都会需要手动编译,本文就可以解决这个问题,感兴趣的可以了解一下
    2023-08-08
  • java实现本地日期时间处理

    java实现本地日期时间处理

    这篇文章主要介绍了本地日期时间处理的程序,实现了下面的功能,大家参考使用吧
    2014-01-01
  • IDEA找不到jdk该如何解决

    IDEA找不到jdk该如何解决

    这篇文章主要给大家介绍了关于IDEA找不到jdk该如何解决的相关资料,刚安装好IDEA后,我们运行一个项目时候,有时候会遇到显示找不到Java的JDK,需要的朋友可以参考下
    2023-11-11
  • springboot+vue 若依项目在windows2008R2企业版部署流程分析

    springboot+vue 若依项目在windows2008R2企业版部署流程分析

    这篇文章主要介绍了springboot+vue 若依项目在windows2008R2企业版部署流程,本次使用jar包启动后端,故而准备打包后的jar文件,需要的朋友可以参考下
    2022-12-12

最新评论