前言
前段时间推出的Java8新特性
文章收到大家广泛关注和好评,非常感谢各位支持,这段时间苦思冥想,决定输出一波
Java多线程技能点,希望可以在大家的工作和面试中有所帮助!本篇文章为多线程系列第一章,主要讲解一下几点:
·多线程好处和应用场景
· 多线程的相关概念和术语
· Java线程创建方式
· Thread类详解,线程的常用方法
· 线程5种状态和6种状态,两种版本解释
· 线程状态之间转换
Java设计者写过一个很有影响力的白皮书,用来解释设计的初衷,并发布了一个简短的摘要,分为11个术语:
· 简单性
· 面向对象
· 分布式
· 健壮性
· 安全性
· 体系结构中立
· 可移植性
· 解释型
· 高性能
· 多线程
· 动态性
其中多线程就是本次要接触的,白皮书中对多线程的解释:
多线程可以带来更好的交互响应和实时行为。
如今,我们非常关注并发性,因为摩尔定律行将完结。我们不再追求更快的处理器,而是着眼于获得更多的处理器,而且要让它们一直保持工作。不过,可以看到,大多数编程语言对于这个问题并没有显示出足够的重视。Java在当时很超前。它是第一个支持并发程序设计的主流语言。从白皮书中可以看到,它的出发点稍有些不同。当时,多核处理器还很神秘,而Web编程才刚刚起步,处理器要花很长时间等待服务器响应,需要并发程序设计来确保用户界面不会"冻住"。并发程序设计绝非易事,不过Java在这方面表现很出色,可以很好地管理这个工作。
在
操作系统中有多任务【multitasking】,在同一刻运行多个程序【应用】的能力。例如,在听音乐的同时可以边打游戏,边写代码。如今我们的电脑大多都是多核CPU,但是,并发执行的进程【正在执行的应用】数目并不是由CPU数目制约的。操作系统将CPU的时间片分配给每一个进程,给人并行处理的感觉。
相关概念
程序【program】:为了完成特定任务,用某种语言编写的一组指令的集合。程序就是一堆代码,一组数据和指令集,是一个静态的概念。就说我们程序员写的那玩意。比如:安装在电脑或者
手机上的各种软件,今日头条、抖音、懂车帝等,如果一个程序支持多线程,这个程序就是一个多线程程序
进程【Process】:是程序的一次执行过程或者说是正在运行的程序,是一个动态概念,进程存在生命周期,也就是说程序随着程序的终止而销毁
线程【Thread】:线程是进程中的实际运作的单位,是进程的一条流水线,是程序的实际执行者,是最小的执行单位。通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程。线程是CPU调度和执行的最小单位
CPU时间片:时间片即CPU分配给各个程序的时间,每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,使各个程序从表面上看是同时进行的,如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。而不会造成CPU资源浪费
并行【parallel】:多个任务同时进行,并行必须有多核才能实现,否则只能是并发,比如:多名学生有问题,同时有多名老师可以辅导解决。
串行【serial】:一个程序处理完当前进程,按照顺序接着处理下一个进程,一个接着一个进行,比如:多名学生有问题,只有一名老师,需要挨个解决。
并发【concurrency】:同一个对象被多个线程同时操作。(这是一种假并行。即一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉),比如:多名学生有问题,只有一个老师,他一会处理A同学,一会处理B同学,一会处理C同学,频繁切换,看起来好似在同时处理学生问题。
多线程意义
实际应用中,多线程非常有用,例如,QQ音乐就是一个多线程程序,我们可以一边听音乐,一般下载音乐,还可以同时播放MV等非常方便。一个Web服务器通过多线程同时处理多个请求,比如Tomcat就是多线程的。
注意:程序会因为引入多线程而变的复杂,多线程同时会带来一些问题,需要我们解决
多线程应用场景
· 程序需要同时执行两个或多个任务。
· 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
· 需要一些后台运行的程序时。
多线程多数在
浏览器、Web服务器、
数据库、各种专用服务器【如游戏服务器】、分布式计算等场景出现。
在使用Java编写后台服务时,如果遇到并发较高、需要后台任务、需要长时间处理
大数据等情况都可以创建线程单独的线程处理这些事项,多线程的目的就在于提高处理速度,减少用户等待时间
· 大量用户请求,创建多个线程共同处理请求
· 下载大文件,可以创建单独的线程,不影响正常的业务流畅度
......
多线程创建方式
线程创建有4种方式:
方式1:继承Thread类
方式2:实现Runnable接口
方式3:实现Callable接口
方式4:使用线程池【这块后边单独说,它更像是管理线程的手段】
继承Thread
步骤:
· 自定义类继承Thread类
· 重写run方法,run方法就是线程的功能执行体
· 创建线程对象,调用start方法启动线程
· 启动线程之后,线程不一定会立即执行,需要得到CPU分配的时间片,也就是拿到CPU执行权限才会执行
JDK源码中,Thread类定义实现了Runnable接口。
所以知道重写的run方法从哪来的了吧!就是从Runnable接口中来的。
需求:创建线程计算10以内的偶数。
线程类:
public class ThreadTest extends Thread{
// run方法是 线程体,启动线程时会运行run()方法中的代码
@Override
public void run() {
// 输出10以内偶数
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
测试类中输出了一句话:主线程。
public class ThreadMain {
public static void main(String[] args) {
// 1、创建线程对象
ThreadTest t1 = new ThreadTest();
// 2、调用start方法启动线程
t1.start();
System.out.println("主线程");
}
}
打印结果:
实现Runnable接口
步骤:
·自定义类实现Runnable接口
· 实现run方法
· 创建实现类对象
· 创建Thread对象,在构造方法中传入实现类对象作为参数
· 调用Thread对象的start方法启动线程
同样的需求打印10以内的偶数
实现类:
public class RunnableImpl implements Runnable{
@Override
public void run() {
// 输出10以内偶数
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
测试类:
public class RunnableMain {
public static void main(String[] args) {
// 1、创建实现类对象
RunnableImpl runnable = new RunnableImpl();
// 2、创建线程对象,接收实现类,因为实现类中的run方法承载了线程的功能
Thread t1 = new Thread(runnable);
// 3、启动线程
t1.start();
// 主线程
System.out.println("主线程");
}
}
Callable接口
FutureTask类:
RunnableFuture接口:
步骤:
·新建类实现Callable接口,并指定泛型类型,类型就是线程计算之后的结果的类型
· 实现call方法,call方法跟run方法类似,不过该方法有返回值和异常抛出,都是来封装线程功能体的
· 在测试类中创建实现类对象,并且创建 FutureTask 对象将实现类对象当做构造方法参数传入
· 创建Thread线程对象,将 FutureTask 对象当做构造方法参数传入,并调用start方法启动线程
可以通过 FutureTask 对象的get方法获取线程的运算结果
案例:还是计算10以内的偶数,这一次将计算结果返回,因为有多个数据所以返回数据用集合存储,则Callable接口的泛型类型应该是集合
实现类:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
// 1、实现Callable,指明泛型类型
public class CallableImpl implements Callable<List<Integer>> {
// 2、线程返回Integer类型数据,抛出异常
@Override
public List<Integer> call() throws Exception {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
// 3、偶数存储到集合中
list.add(i);
}
}
// 4、返回集合
return list;
}
}
测试类:
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableMain {
public static void main(String[] args) {
// 1、创建Callable实现类对象
CallableImpl callable = new CallableImpl();
// 2、创建 FutureTask对象传入 callable
// FutureTask 实现 了 RunnableFuture,RunnableFuture实现了Runnable接口和Future接口
FutureTask<List<Integer>> task = new FutureTask<>(callable);
// 3、将 task 传入线程对象
Thread t1 = new Thread(task);
// 4、启动线程
t1.start();
// 5、获取线程返回数据
try {
List<Integer> list = task.get();
System.out.println(list);
} catch (Exception e) {
e.printStackTrace();
}
}
}