目录
前言
一.Java的多线程
1.1多线程的认识
1.2Java多线程的创建方式
1.3Java多线程的生命周期
1.4Java多线程的执行机制
二.创建多线程的四种方式
2.1继承Thread类
⭐创建线程
⭐Thread的构造方法和常见属性
2.2.实现Runnable接口
⭐创建线程
⭐使用lambda表达式创建
2.3实现Callable接口创建多线程
⭐线程的创建
⭐Callable接口的特点
2.4通过线程池创建多线程
⭐创建线程
个人主页:tq02的博客_CSDN博客-C语言,Java,Java数据结构领域博主
本文由 tq02 原创,首发于 CSDN
本章讲解内容:多线程的认识、创建方式、及其状态
学习专栏: C语言 JavaSE MySQL基础
在学习多线程之前,我们必须了解什么是线程?作用是什么?而线程的知识又与进程有关系,因此我们需要先了解进程再去了解线程,这样才能更好的学习到多线程的知识。本文只是多线程的一部分,多线程涉及的知识点很多很多,锁啊、线程安全啊、CAS等知识,需要耐心学习。
进程学习:http://t.csdn.cn/I4uDU
线程学习:http://t.csdn.cn/AxYac
多线程,从字面上理解,就是从多个单线程一起执行多个任务。在Java 编程中,已经给多线程编程提供了内置的支持。多线程是多任务的一种特别的形式,但多线程使用了更小的cpu资源开销。 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
线程本身就是操作系统提供的概念,因此操作系统提供了一些API供程序员使用,而在Java中,也存在一些API供人们使用和编译。在Java标准库中Thread类就是用于多线程的创建。
注:创建多线程的方式不仅仅只有Thread类
Java语言中,目前可以创建多线程的方式有四种方式:
以上的1、2、5方法可以搭配匿名内部类使用,而目前最为常用的方式有:实现Runnable接口、调用多线程池。
新建状态、可执行状态、执行状态以及死亡状态是每一个线程都会发生,而阻塞状态则是选择性发生,当需要阻塞时,使用sleep()、join()方法。
当Java程序运行时,先创建出一个进程,该进程里至少包含一个线程,主线程,就是负责执行main方法的线程。然后在mian()方法里创建出其他线程。我们主要学习的就是创建和使用线程。
注:一般情况下,主线程与子线程相互不影响,即子线程结束,主线程不一定结束;主线程结束,子线程不一定结束;主线程异常,子线程不一定异常;子线程异常,主线程不一定异常。但当设置守护线程等特殊操作时,主线程与子线程会发生相互影响。
使用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(Runnable 对象名) | 使用Runnable对象创建线程 |
Thread(String 线程名) | 创建线程对象,并命名 |
Thread(Runnable 对象名,String 线程名) | 使用Runnable对象创建线程对象,并命名 |
常见属性:
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否有后台线程 | isDaemon() |
是否存活 | isAlive() |
是否中断 | isInterrupted() |
ID 是线程的唯一标识,不同线程不会重复
名称是各种调试工具用到
其中重要的是前台和后台线程:
Java多线程当中默认为前台线程,也可以通过setDaemon方法设置,false
存活是指线程是否执行结束。中断是指让正在执行的线程强行结束。类似循环的break;
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表达式用于匿名内部类和接口的情况,而创建Thread类时,搭配匿名内部类创建了Runnable子类对象,因此可以使用lambda表达式的方法简化操作。
lambda表达式的学习:http://t.csdn.cn/VxRLy
代码示例:
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t4 = new Thread(() -> {
System.out.println("使用匿名类创建 Thread 子类对象");
});
如果看不懂lambda表达式,你就记住这是一种创建线程的方法,不去理解。
注:相比前几种创建方式,使用lambda表达式创建更为方便简单。
Callable 是一个 interface . 使用时需要创建utureTask类的对象,相当于把线程封装了一个 "返回值". 方便程序猿借助多线程的方式计算结果.该方法是基于jdk5.0之后新增的方法,
方法步骤:
代码示例:
Callable callable = new Callable() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
FutureTask futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
int result = futureTask.get();
System.out.println(result);
虽然创建销毁线程比创建销毁进程更轻量, 但是在频繁创建销毁线程的时候还是会比较低效.
线程池就是为了解决这个问题. 如果某个线程不再使用了, 并不是真正把线程释放, 而是放到一个 "池子"中, 下次如果需要用到线程就直接从池子中取, 不必通过系统来创建了.
1、线程池的使用,需要使用以下类:
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枚举中。
- 初始状态(NEW):线程对象被创建但尚未调用start()方法。
- 就绪状态(RUNNABLE):线程处于可运行线程池中,等待被线程调度选中获取CPU的使用权。就绪状态分为两种情况,一是线程对象创建后,其他线程调用了该对象的start()方法,此时线程处于某个线程拿到对象锁、当前线程时间片用完调用yield()方法、锁池里的线程拿到对象锁后,这些线程也将进入就绪状态。
- 运行状态(RUNNABLE之RUNNING):线程正在被执行,具体来说就是线程获得了CPU的使用权。
- 阻塞状态(BLOCKED):线程阻塞于锁,即线程在等待获得对象锁。
- 等待状态(WAITING):线程需要等待其他线程做出一些特定动作,比如等待其他线程的通知或中断。
- 超时等待状态(TIMED_WAITING):与等待状态不同的是,超过指定时间后会自动返回。
- 终止状态(TERMINATED):线程的run()方法执行完成,或者主线程的main()方法执行完成,线程终止。终止状态的线程不能再被复生。如果在终止状态的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
注:之前的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着
的
状态图:
多线程的概念、创建、状态的讲解本章已经结束了,而后期还有很多线程操作、线程安全、锁等知识,因此多线程是一个极为复杂并且重要的知识。