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()方法,就是你和你朋友同时在取钱,取钱到一半你说先停下来,我朋友那边取完了你再取。

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

目录
相关文章
|
2天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
24 6
|
15天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
10天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
10天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
30 3
|
11天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
15天前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
24 2
|
15天前
|
监控 Java 开发者
Java线程管理:守护线程与本地线程的深入剖析
在Java编程语言中,线程是程序执行的最小单元,它们可以并行执行以提高程序的效率和响应性。Java提供了两种特殊的线程类型:守护线程和本地线程。本文将深入探讨这两种线程的区别,并探讨它们在实际开发中的应用。
20 1
|
16天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
52 1
|
Java
华为技术专家深度解析Java线程状态(下)
华为技术专家深度解析Java线程状态
130 0
华为技术专家深度解析Java线程状态(下)
|
26天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####