Java多线程(1)---多线程认识、四种创建方式以及线程状态

简介: Java多线程(1)---多线程认识、四种创建方式以及线程状态

前言

       在学习多线程之前,我们必须了解什么是线程?作用是什么?而线程的知识又与进程有关系,因此我们需要先了解进程再去了解线程,这样才能更好的学习到多线程的知识。本文只是多线程的一部分,多线程涉及的知识点很多很多,锁啊、线程安全啊、CAS等知识,需要耐心学习。

进程学习:http://t.csdn.cn/I4uDU

线程学习:http://t.csdn.cn/AxYac

一.Java的多线程

1.1多线程的认识

    多线程,从字面上理解,就是从多个单线程一起执行多个任务。在Java 编程中,已经给多线程编程提供了内置的支持。多线程是多任务的一种特别的形式,但多线程使用了更小的cpu资源开销。 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。


       线程本身就是操作系统提供的概念,因此操作系统提供了一些API供程序员使用,而在Java中,也存在一些API供人们使用和编译。在Java标准库中Thread类就是用于多线程的创建。

注:创建多线程的方式不仅仅只有Thread类

1.2Java多线程的创建方式

Java语言中,目前可以创建多线程的方式有四种方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 使用lambda表达式(基于Runnable接口搭配内部类的优化)
  1. 使用线程池
  2. 使用FutureTask类和Callable接口

以上的1、2、5方法可以搭配匿名内部类使用,而目前最为常用的方式有:实现Runnable接口、调用多线程池。

1.3Java多线程的生命周期

        新建状态、可执行状态、执行状态以及死亡状态是每一个线程都会发生,而阻塞状态则是选择性发生,当需要阻塞时,使用sleep()、join()方法。

1.4Java多线程的执行机制

       当Java程序运行时,先创建出一个进程,该进程里至少包含一个线程主线程,就是负责执行main方法的线程。然后在mian()方法里创建出其他线程。我们主要学习的就是创建和使用线程。

注:一般情况下,主线程与子线程相互不影响,即子线程结束,主线程不一定结束;主线程结束,子线程不一定结束;主线程异常,子线程不一定异常;子线程异常,主线程不一定异常。但当设置守护线程等特殊操作时,主线程与子线程会发生相互影响。


 

二.创建多线程的四种方式

2.1继承Thread类

⭐创建线程

       使用Thread类创建线程有2种方式,最基本的实现多线程方式,就是创建一个类继承Thread类,然后再实例化该类。可实例化多个线程。

1.继承Thread类创建一个线程

class MyThread extends Thread {
    @Override
    public void run() {
    System.out.println("这里是线程运行的代码");
    }
}
public class Text{
     public static void main(String[] args) {
        //创建MyThread实例
      MyThread t1=new MyThread();
       //调用start方法启动线程
     t1.start();
    }
}      

注:继承Thread类需要重写run()方法,而调用的是start()方法,而不是run()方法,start()是启动线程,而run()则是执行方法。

2.匿名内部类创建 Thread 子类对象

// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
    @Override
    public void run() {
    System.out.println("使用匿名类创建 Thread 子类对象");
    }
};
    //启动线程
    t1.start();

:该方法不需要继承其他类,而是直接实例化Thread类和使用匿名内部类。

⭐Thread的构造方法和常见属性

Thread构造方法:

构造方法 解释
Thread() 创建线程
Thread(Runnable 对象名) 使用Runnable对象创建线程
Thread(String 线程名) 创建线程对象,并命名
Thread(Runnable 对象名,String 线程名) 使用Runnable对象创建线程对象,并命名

常见属性:

属性 获取方法
ID getId()
名称 getName()
状态 getState()
优先级 getPriority()
是否有后台线程 isDaemon()
是否存活 isAlive()
是否中断 isInterrupted()

ID 是线程的唯一标识,不同线程不会重复

名称是各种调试工具用到

其中重要的是前台和后台线程

  1. 前台线程,会影响到进程结束,如果前台进程没有执行完毕,进程不会结束
  2. 后台线程,也称守护线程,当主线程结束时,进程结束,后台线程无论是否还在执行也结束

Java多线程当中默认为前台线程,也可以通过setDaemon方法设置,false

存活是指线程是否执行结束。中断是指让正在执行的线程强行结束。类似循环的break;

2.2.实现Runnable接口

⭐创建线程

       Runnable接口,将需要执行的线程放入其中,再通过Runnable和Thread配合,就可以进行线程的实现。而方法有2种。

第一种:创建Thread类实例,调用构造方法时,将Runnable对象传参

class MyRunnable implements Runnable {
    @Override
    public void run() {
    System.out.println("这里是线程运行的代码");
    }
}
    //创建Thread类实例
Thread t = new Thread(new MyRunnable());
    //调用start()方法
    t.start();

实现一个接口Runnable,然后重写run方法,再将Runnable实例化出的对象,放入Thread的构造函数Thread(Runnable 对象名),就可以实现线程的执行。

第二种:创建Thread类实例,搭配使用匿名内部类创建Runnable子类对象

// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Runnable 子类对象");
    }
});

  这种方法更为的简便,直接在匿名内部类当中重写run()方法,在run()方法内部写上需要执行的操作。


⭐使用lambda表达式创建

       lambda表达式用于匿名内部类和接口的情况,而创建Thread类时,搭配匿名内部类创建了Runnable子类对象,因此可以使用lambda表达式的方法简化操作。

lambda表达式的学习http://t.csdn.cn/VxRLy

代码示例:

// 使用 lambda 表达式创建 Runnable 子类对象
Thread t4 = new Thread(() -> {
    System.out.println("使用匿名类创建 Thread 子类对象");
});

如果看不懂lambda表达式,你就记住这是一种创建线程的方法,不去理解。

注:相比前几种创建方式,使用lambda表达式创建更为方便简单。

2.3实现Callable接口创建多线程

⭐线程的创建

Callable 是一个 interface . 使用时需要创建utureTask类的对象,相当于把线程封装了一个 "返回值". 方便程序猿借助多线程的方式计算结果.该方法是基于jdk5.0之后新增的方法,

方法步骤:

  1. 创建一个实现Callable接口的类。
  2. 在这个实现类中实现Callable接口的call()方法,并创建这个类的对象。      
  3. 将这个Callable接口实现类的对象作为参数传递到FutureTask类的构造器中,创建FutureTask类的对象。      
  4. 将这个FutureTask类的对象作为参数传递到Thread类的构造器中,创建Thread类的对象,并调用这个对象的start()方法。
  1. 在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结
    果。

代码示例:

Callable<Integer> callable = new Callable<Integer>() {
        @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 1000; i++) {
        sum += i;
    }
    return sum;
    }
};
    FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
            t.start();
        int result = futureTask.get();
        System.out.println(result);

⭐Callable接口的特点

  1. Callable 和 Runnable 相对, 都是描述一个 "任务". Callable 描述的是带有返回值的任务,Runnable 描述的是不带返回值的任务.
  2. Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为
  1. Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定.
  2. FutureTask 就可以负责这个等待结果出来的工作.

2.4通过线程池创建多线程

 虽然创建销毁线程比创建销毁进程更轻量, 但是在频繁创建销毁线程的时候还是会比较低效.

线程池就是为了解决这个问题. 如果某个线程不再使用了, 并不是真正把线程释放, 而是放到一个 "池子"中, 下次如果需要用到线程就直接从池子中取, 不必通过系统来创建了.

⭐创建线程

1、线程池的使用,需要使用以下类:

  • Executors 是一个工厂类, 能够创建种不同风格的线程池.
  • ExecutorService 表示一个线程池实例.
  • ExecutorService 的 submit 方法能够向线程池中提交若干个任务.

2、Executors 创建的几种风格线程池:

实例化一个线程池格式:ExecutorService pool = Executors.方法();

创建线程池种类 解释
 Executors.newFixedThreadPool(int  Num threads)  创建固定线程数的线程池
Executors.newSingleThreadExecutor() 创建只包含单个线程的线程池.
Executors.newCachedThreadPool() 创建线程数目动态增长的线程池.
Executors.newScheduledThreadPool(int corePoolSize) 

设定延迟时间后执行命令,或者定期执行命令. 是

进阶版的 Timer.

注:Executors 本质上是 ThreadPoolExecutor 类的封装

代码示例:

ExecutorService pool = Executors.newFixedThreadPool(10);
    pool.submit(new Runnable() {
        @Override
    public void run() {
        System.out.println("hello");
    }
});
    //线程池使用结束之后需要使用shutdown()关闭
     pool.shutdown();

三.线程的状态

       线程的状态是一个枚举类型 Thread.State,而线程的状态一共有6种,6种状态定义在Thread类的State枚举中。

  1. 初始状态(NEW):线程对象被创建但尚未调用start()方法。
  2. 就绪状态(RUNNABLE):线程处于可运行线程池中,等待被线程调度选中获取CPU的使用权。就绪状态分为两种情况,一是线程对象创建后,其他线程调用了该对象的start()方法,此时线程处于某个线程拿到对象锁、当前线程时间片用完调用yield()方法、锁池里的线程拿到对象锁后,这些线程也将进入就绪状态。
  3. 运行状态(RUNNABLE之RUNNING):线程正在被执行,具体来说就是线程获得了CPU的使用权。
  4. 阻塞状态(BLOCKED):线程阻塞于锁,即线程在等待获得对象锁。
  5. 等待状态(WAITING):线程需要等待其他线程做出一些特定动作,比如等待其他线程的通知或中断。
  6. 超时等待状态(TIMED_WAITING):与等待状态不同的是,超过指定时间后会自动返回。
  7. 终止状态(TERMINATED):线程的run()方法执行完成,或者主线程的main()方法执行完成,线程终止。终止状态的线程不能再被复生。如果在终止状态的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。


注:之前的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着

 

3.1状态的抽象说明

状态图:

 

  1. 初始状态(NEW):刚刚拿到银行卡
  2. 就绪状态(RUNNABLE):排队等待中
  3. 运行状态(RUNNABLE之RUNNING)):开始与柜台人员进行交流取钱
  4. 阻塞状态(BLOCKED):阻塞了,相当于有人提前预约了取钱,你只能等待他结束
  5. 等待状态(WAITING):使用了wait()等待,例如柜台人员有一点事情,让你等到他回来
  1. 超时等待状态(TIMED_WAITING):使用了sleep(),柜台人员告诉了你等他多久;使用join()方法,就是你和你朋友同时在取钱,取钱到一半你说先停下来,我朋友那边取完了你再取。

 多线程的概念、创建、状态的讲解本章已经结束了,而后期还有很多线程操作、线程安全、锁等知识,因此多线程是一个极为复杂并且重要的知识。

目录
相关文章
|
1天前
|
存储 缓存 前端开发
Java串口通信技术探究3:RXTX库线程 优化系统性能的SerialPortEventListener类
Java串口通信技术探究3:RXTX库线程 优化系统性能的SerialPortEventListener类
10 3
|
1天前
|
Java
JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识
JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识。入坑JAVA因它的面向对象特性、平台无关性、强大的标准库和活跃的社区支持。
9 2
|
1天前
|
Java 调度 开发者
Java中的多线程编程:基础与实践
【5月更文挑战第2天】本文将深入探讨Java中的多线程编程,从基础概念到实际应用,为读者提供全面的理解和实践指导。我们将首先介绍线程的基本概念和重要性,然后详细解析Java中实现多线程的两种主要方式:继承Thread类和实现Runnable接口。接着,我们将探讨线程同步的问题,包括synchronized关键字和Lock接口的使用。最后,我们将通过一个实际的生产者-消费者模型来演示多线程编程的实践应用。
|
1天前
|
安全 Java 程序员
Java中的多线程编程:从理论到实践
【5月更文挑战第2天】 在计算机科学中,多线程编程是一项重要的技术,它允许多个任务在同一时间段内并发执行。在Java中,多线程编程是通过创建并管理线程来实现的。本文将深入探讨Java中的多线程编程,包括线程的概念、如何创建和管理线程、以及多线程编程的一些常见问题和解决方案。
8 1
|
2天前
|
存储 安全 Java
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第1天】本文将深入探讨Java并发编程的核心概念,包括线程安全和性能优化。我们将详细分析线程安全问题的根源,以及如何通过合理的设计和编码实践来避免常见的并发问题。同时,我们还将探讨如何在保证线程安全的前提下,提高程序的并发性能,包括使用高效的同步机制、减少锁的竞争以及利用现代硬件的并行能力等技术手段。
|
2天前
|
并行计算 Java 数据处理
Java中的多线程编程:基础知识与实践
【5月更文挑战第1天】本文将深入探讨Java中的多线程编程,包括其基本概念、实现方式以及实际应用。我们将从理论和实践两个角度出发,详细解析线程的创建、启动、控制以及同步等关键问题,并通过实例代码演示如何在Java中有效地使用多线程。
|
2天前
|
Java 程序员
Java中的多线程编程:从理论到实践
【5月更文挑战第1天】 在现代计算机科学中,多线程编程是一个重要的概念,它允许程序员在同一程序中并行运行多个任务。Java作为一种广泛使用的编程语言,提供了一套丰富的多线程编程工具。本文将介绍Java中多线程编程的基本概念,包括线程的创建、启动、控制和同步,以及一些常见的多线程问题和解决方案。
|
2天前
|
存储 Java 程序员
Java中的多线程编程:基础知识与实践
【5月更文挑战第1天】在现代计算机科学中,多线程是一种重要的并行计算技术,允许多个执行流程并发运行。本文将深入探讨Java语言中的多线程编程,从基础概念到实际应用,帮助读者理解多线程的核心原理,并通过实例学习如何在Java中创建和管理线程。我们将涵盖线程的生命周期、同步机制以及如何利用高级类如Executor框架来优化多线程应用的性能。通过本文的学习,读者将具备设计和实现高效、稳定多线程Java应用程序的能力。
6 2
|
2天前
|
缓存 Java 调度
Java并发编程:深入理解线程池
【4月更文挑战第30天】 在Java并发编程中,线程池是一种重要的工具,它可以帮助我们有效地管理线程,提高系统性能。本文将深入探讨Java线程池的工作原理,如何使用它,以及如何根据实际需求选择合适的线程池策略。
|
7月前
|
设计模式 缓存 Java
Java多线程线程池:提升应用性能的终极利器
Java多线程线程池:提升应用性能的终极利器
571 0