Java多线程详解
1、程序、进程、现场
1.1程序
程序是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
1.2进程
- 进程是程序的一次执行过程(正在运行的一个程序),是一个动态的过程,有它自身的生产、存在、消亡的过程。——生命周期;
- 进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。
- 程序是静态的,进程是动态的;
- 进程作为资源分配(内存、CPU)基本的单位,系统在运行时会为每个进程分配不同的内存区域。
1.3线程
-
进程可进一步细化为线程,是一个程序内部的一条执行路径。
-
线程是进程中执行运算的最小单位,亦是调度运行的基本单位。
-
若一个进程同一时间并行或并发执行多个线程,就是支持多线程的;
-
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小;
-
一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆栈中分配对象,可以访问相同的变量和对象,这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全隐患。
1.4并行和并发
并行:多个cpu同时执行多个任务。比如:多个人同时做不同的事。
并发:一个cpu(采用时间片)同时执行多个任务。
1.5单核CPU和多核CPU
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。但是因为CPU时间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
一个Java应用程序java.exe,其实至少有三个线程: main()主线程, gc)垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
2、线程的创建和使用
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现…
在Java的JDK开发包中,已经自带了对多线程技术的支持,可以很方便地进行多线程编程。
Thread类的特性
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。
2.1Thread类
Thread类也是实现Runable借口
构造器(以JDK14为例)
Thread():创建新的Thread对象
Thread(String threadname):创建线程并指定线程实例名
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
Thread(Runnable target, String name):创建新的Thread对象
实现多线程编程的方式有两种,一种是继承 Thread 类,另一种是实现 Runnable 接口。
方式一:继承Thread类
1)定义子类继承Thread类。
2)子类中重写Thread类中的run方法。
3)创建Thread子类对象,即创建了线程对象。
4)调用线程对象start方法:启动线程,调用run方法。
package com.example.springbootdemo.threadexamp;
public class MyThread extends Thread {
public MyThread() {
super();
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("子线程"+i);
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("线程创建完毕");
}
}
1,如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
2, run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
3,想要启动多线程,必须调用start方法。
4·一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常"lllegalThreadStateException"。
匿名内部类Thread
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("匿名内部类子线程"+i);
}
}
}.start();
System.out.println("匿名内部类线程创建完毕");
}
方式二:实现Runnable接口
Runable接口里只有一个抽象run接口,要实现多线程必须重写run方法,线程的执行体
1)定义子类,实现Runnable接口。
2)子类中重写Runnable接口中的run方法。
3)通过Thread类含参构造器创建线程对象。
4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
5)调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
package com.example.springbootdemo.threadexamp;
public class MyRuanableThread implements Runnable {
public MyRuanableThread() {
super();
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("runable 子线程"+i);
}
}
public static void main(String[] args) {
MyRuanableThread myRuanableThread = new MyRuanableThread();
Thread thread = new Thread(myRuanableThread);
thread.start();
System.out.println("线程创建完毕");
}
}
匿名内部类实现Runable
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("runable 匿名内部子线程" + i);
}
}
}).start();
System.out.println("runable匿名内部类线程创建完毕");
}
另外一种实现方式:
new Thread(() -> System.out.println()).start();
新增方式一:实现Callable接口
-
Callable接口
java.util.concurrent.Callable是一个泛型接口,只有一个call()方法
与使用Runnable相比, Callable功能更强大些
相比run()方法,可以有返回值,且返回一个指定的泛型类对象
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask实现类的支持,用于接收运算结果 -
Future接口
可以对具体Runnable, Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask是Futrue接口的唯一的实现类
FutureTask实现了RunnableFuture接口,RunnableFuture接口同时继承了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
- 使用Callable接口实现多线程的步骤
(1)第一步:创建Callable子类的实例化对象
(2)第二步:创建FutureTask对象,并将Callable对象传入FutureTask的构造方法中
(注意:FutureTask实现了Runnable接口和Future接口)
(3)第三步:实例化Thread对象,并在构造方法中传入FurureTask对象
(4)第四步:启动线程
package com.example.threaddemo.thread;
import org.springframework.core.io.FileUrlResource;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallableThread implements Callable {
public MyCallableThread() {
super();
}
@Override
public Object call() throws Exception {
for (int i = 0; i < 3; i++) {
System.out.println("Callable 子线程"+i);
}
return null;
}
public static void main(String[] args) {
MyCallableThread myCallableThread = new MyCallableThread();
FutureTask futureTask = new FutureTask(myCallableThread);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println("Callable线程创建完毕");
}
}
获取线程的返回值:
package com.example.threaddemo.thread;
import org.springframework.core.io.FileUrlResource;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallableThread implements Callable<Integer> {
public MyCallableThread() {
super();
}
@Override
public Integer call() throws Exception {
Integer sum = 0;
for (int i = 0; i < 3; i++) {
sum+=i;
System.out.println("Callable 子线程"+i);
}
return sum;
}
public static void main(String[] args) {
MyCallableThread myCallableThread = new MyCallableThread();
FutureTask<Integer> futureTask = new FutureTask<Integer>(myCallableThread);
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("Callable线程创建完毕");
}
}
匿名类部类实现Callable接口
public static void main(String[] args) throws Exception{
Callable<String> cl = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + "正在行军");
System.out.println(Thread.currentThread().getName() + "遭遇敌军");
System.out.println(Thread.currentThread().getName() + "奋勇杀敌!");
return "战斗胜利,俘虏敌军50000人";
}
};
FutureTask<String> ft = new FutureTask(cl);
new Thread(ft, "李胜利部队").start();
System.out.println(ft.get());
System.out.println("匿名内部类创建成功");
}
新增方式二:使用线程池
链接: 四种线程池使用.
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完改回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
• 提高响应速度(减少了创建新线程的时间)
• 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
• 便于线程管理
corePoolSize:线程池初始化时线程的个数
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池相关API、
JDK5.0起提供了线程池相关API:ExecutorService和Executors
- ExecutorService:真正的线程池接口,继承Executor接口
是Java提供的用于管理线程池的接口。该接口的两个作用:控制线程数量和重用线程
常见子类:ThreadPoolExecutor
public void execute(Runnable command):执行任务和命令,一般用来执行Runable
public void shutdown():关闭连接池 - Executors:工具类、线程池的工厂类,用于创建和返回不同类型的线程池
(除了newScheduledThreadPool返回值都是ExecutorService,newScheduledThreadPool返回的是ScheduledExecutorService,该接口继承ExecutorService)
Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。
Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行;
Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
2.2继承方式和实现方式的联系和区别
JDK14
public class Thread implements Runnable {}
区别
- 继承Thread:线程代码存放Thread子类run方法中。
- 实现Runnable:线程代码存在接口的子类的run方法。
实现方式的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
Thread类的有关方法
void start():启动线程,并执行对象的run()方法
run():线程在被调度时执行的操作
String getName):返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
static void yield():线程让步
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
若队列中没有同优先级的线程,忽略此方法
.join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止,低优先级的线程也可以获得执行
static void sleep(long millis): (指定时间:毫秒)
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常
stop():强制线程生命期结束,不推荐使用
boolean isAlive():返回boolean,判断线程是否还活着
3 线程的调度
线程调度
计算机通常只有一个cpu,在任意时刻只能执行一条机器指令,每个线程只有获得cpu的使用权才能执行指令.所谓多线程的并发运行,其实是从宏观上看,各个线程轮流获取cpu的使用权,分别执行各自的任务.在运行池中,会有多个处于就绪状态的线程在等待cpu, JAVA虚拟机的一项任务就是负责线程的调度.
线程调度是指按照特定机制为多个线程分配CPU的使用.
调度方式
-
分时调度模式: 是指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的cpu的时间片.
-
抢占式调度模式: JAVA虚拟机采用抢占式调度模式,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那就随机选择一个线程,使其占用CPU.处于运行状态的线程会一直运行,直至它不得不放弃CPU.
抢占式策略只是表示高优先级执行的概率比较大,低优先级执行的概率低,低优先级也有可能执行。
4 线程的优先级
线程的优先级等级
/**
* The minimum priority that a thread can have.
* 最小优先级
*/
public static final int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
* 默认优先级
*/
public static final int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
* 最大优先级
*/
public static final int MAX_PRIORITY = 10;
涉及方法:
getPriority();返回线程的优先级
setPriority(int newPriority),设置线程的优先级
说明
线程创建时继承父线程的优先级;
低优先级只是获取调度概率比较低,并不一定是在高优先级线程之后才被调用;
5 线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开,也就是JVM是否正常退出。
JVM正常退出是与异常退出相对的概念,异常退出如调用System.exit(status)退出JVM进程,调用Linux的kill命令杀死进程等。
JVM正常退出的条件是JVM中所有非守护线程结束任务,即使还有守护线程在运行。
• 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
• Java垃圾回收就是一个典型的守护线程。
• 若JVM中都是守护线程,当前JVM将退出。
• 子线程会在默认情况下继承父线程的类别,如果父线程是守护线程,子线程也是守护线程。当然可以通过setDaemon方法改变属性。
6 线程的生命周期
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下五种状态:
• 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
• 就绪:线程对象创建后,处于新建状态的线程被其他线程调用了自己的start()后,将进入线程队列(可运行线程池)中等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源。
即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
• 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能。
• 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,放弃CPU使用权,让出CPU并临时中止自己的执行,进入阻塞状态。直到线程进入就绪状态,才有机会转到运行状态。
• 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束。