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
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
2天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
9 3
|
1天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
2天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
7 1
|
2天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
2天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
20 1
|
6月前
|
安全 Java
深入理解Java并发编程:线程安全与性能优化
【2月更文挑战第22天】在Java并发编程中,线程安全和性能优化是两个重要的主题。本文将深入探讨这两个主题,包括线程安全的基本概念,如何实现线程安全,以及如何在保证线程安全的同时进行性能优化。
55 0
|
6月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
3月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
63 1
|
4月前
|
安全 Java 开发者
Java并发编程中的线程安全问题及解决方案探讨
在Java编程中,特别是在并发编程领域,线程安全问题是开发过程中常见且关键的挑战。本文将深入探讨Java中的线程安全性,分析常见的线程安全问题,并介绍相应的解决方案,帮助开发者更好地理解和应对并发环境下的挑战。【7月更文挑战第3天】
89 0
下一篇
无影云桌面