第5章 多线程
一、进程和线程
1、进程和线程的相关概念
进程(Process)
是计算机中的程序关于某数据集合上的一次运行活动 是系统进行资源分配的基本单位 简单理解:程序的执行过程
1.独立性:每一个进程都有自己的空间,在没有经过进程本身允 许的情况下,一个进程不可以直接访问其它的的进程空间 2.动态性:进程是动态产生,动态消亡的 3.并发性:任何进程都可以同其它进程一起并发执行
并行:在同一时刻,有多个指令在多个CPU上【同时】执行 并发:在同一时刻,有多个指令在单个CPU上【交替】执行
线程(Thread)
进程可以同时执行多个任务,每个任务就是线程
多线程的意义
随着处理器上的核心数量越来越多,现在大多数计算机都比以往更加擅长并行计算 一个线程,在一个时刻,只能运行在一个处理器核心上
提高效率 可以同时处理多个任务
2、Java程序默认是多线程的
package com.itheima.thread;
public class ThreadDemo1 {
/*
Java程序默认是多线程的.
1. main线程
2. 垃圾回收线程
*/
public static void main(String[] args) {
for(int i = 1; i <= 500000; i++){
new Demo();//当产生一份地址但没有交给任何变量,就会被当作垃圾
}
}
}
class Demo {
@Override
protected void finalize() throws Throwable {//当前这个类的对象被识别为垃圾对象且被当作垃圾处理了
System.out.println("垃圾被清理了...");
}
}
二、线程相关方法

1、继承Thread类
开启线程的第一种方式 - 继承Thread类
-
编写一个类继承Thread类
-
重写run方法
-
将线程任务代码写在run方法中
-
创建线程对象
-
调用start方法开启线程
注意事项: 调用start方法的时候, 会自动调用run方法.
- 只有是调用了start()方法, 才是开启了新的线程.
package com.itheima.thread;
public class ThreadDemo2 {
/*
开启线程的第一种方式 - 继承Thread类
1. 编写一个类继承Thread类
2. 重写run方法
3. 将线程任务代码写在run方法中
4. 创建线程对象
5. 调用start方法开启线程
注意事项: 调用start方法的时候, 会自动调用run方法.
- 只有是调用了start()方法, 才是开启了新的线程.
*/
public static void main(String[] args) {
// 4. 创建线程对象
MyThread mt = new MyThread();
// 5. 调用start方法开启线程
mt.start();
//会在控制台看到主线程和线程交替执行的现象
for (int i = 1; i <= 500; i++) {
System.out.println("main线程执行任务" + i);
}
}
}
// 1. 编写一个类继承Thread类
class MyThread extends Thread {
// 2. 重写run方法
@Override
public void run() {
// 3. 将线程任务代码写在run方法中
for (int i = 1; i <= 500; i++) {
System.out.println("线程任务执行了" + i);
}
}
}
2、实现Runnable接口
开启线程第二种方式 - 实现Runnable接口
-
编写一个类实现Runnable接口
-
重写run方法
-
将线程任务代码, 写在run方法中
-
创建Runnable接口的实现类对象
-
创建线程对象, 并将Runnable接口的实现类对象传入
-
使用线程对象调用start方法开启线程
package com.itheima.thread;
import java.util.concurrent.FutureTask;
public class ThreadDemo3 {
/*
开启线程第二种方式 - 实现Runnable接口
1. 编写一个类实现Runnable接口
2. 重写run方法
3. 将线程任务代码, 写在run方法中
4. 创建Runnable接口的实现类对象
5. 创建线程对象, 并将Runnable接口的实现类对象传入 +++
6. 使用线程对象调用start方法开启线程
*/
public static void main(String[] args) {
// 4. 创建Runnable接口的实现类对象
MyRunnable mr = new MyRunnable();
//尝试调用start方法,失败,因为他不是线程对象,仅仅是一个封装了线程任务的对象
// 5. 创建线程对象, 并将Runnable接口的实现类对象传入
//自己可以开启多条线程
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
// 6. 使用线程对象调用start方法开启线程
t1.start();
t2.start();
}
}
// 1. 编写一个类实现Runnable接口
//第一种实现方式是继承Thread,第二种实现方式是实现Runnable接口
//因为只能单继承,可以多实现,所以第二种使用更广泛
class MyRunnable implements Runnable {
// 2. 重写run方法
@Override
public void run() {
// 3. 将线程任务代码, 写在run方法中
for (int i = 1; i <= 500; i++) {
System.out.println("自己的线程任务" + i);
}
}
}
3、实现Callable接口
需要有结果返回
package com.itheima.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo4 {
/*
开启线程的第三种方式 - 实现 Callable 接口
1. 编写一个类实现Callable接口
2. 重写call方法 (此方法存在返回值)
3. 将线程任务代码写在call方法中
4. 创建线程资源对象
5. 创建线程任务对象, 封装线程资源 +++
6. 创建线程对象, 传入线程任务
7. 使用线程对象调用start开启线程
*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 4. 创建线程资源对象
MyCallable mc = new MyCallable();
// 5. 创建线程任务对象, 封装线程资源
FutureTask<Integer> task1 = new FutureTask<>(mc);
FutureTask<Integer> task2 = new FutureTask<>(mc);
// 6. 创建线程对象, 传入线程任务
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
// 7. 使用线程对象调用start开启线程
t1.start();
t2.start();
//通过task的get方法来接收线程的返回值
Integer result1 = task1.get();
System.out.println("task1 返回的结果为:" + result1);
Integer result2 = task2.get();
System.out.println("task2 返回的结果为:" + result2);
}
}
// 1. 编写一个类实现Callable接口
class MyCallable implements Callable<Integer> {
// 2. 重写call方法 (此方法存在返回值)
@Override
public Integer call() throws Exception {
// 3. 将线程任务代码写在call方法中
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
4、Thread类的常见方法1
线程设置名字和获取名字
Thread类的方法:
public String getName() : 获取线程名字
public void setName() : 设置线程名字
public static Thread currentThread() : 获取当前线程的对象
继承Thread类set、get线程名
package com.itheima.thread_method;
public class ThreadNameDemo1 {
/*
线程设置名字和获取名字
Thread类的方法:
public String getName() : 获取线程名字
public void setName() : 设置线程名字
public static Thread currentThread() : 获取当前线程的对象
*/
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程A: ");
MyThread mt2 = new MyThread("线程B: ");
// mt1.setName("线程A: ");
// mt2.setName("线程B: ");
mt1.start();
mt2.start();
}
}
class MyThread extends Thread {
public MyThread() {//重写空参带参构造
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
//因为父类是Thread,所以可以用父类来getName
System.out.println(super.getName() + "线程任务执行了" + i);
}
}
}
Runnable接口set、get线程名
package com.itheima.thread_method;
public class ThreadNameDemo2 {
/*
线程设置名字和获取名字
Thread类的方法:
public String getName() : 获取线程名字
public void setName() : 设置线程名字
public static Thread currentThread() : 获取当前线程的对象
*/
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr, "线程A");//传入线程对象并且指定名称
t.start();
for (int i = 1; i <= 200; i++) {
System.out.println(Thread.currentThread().getName() + "线程执行了" + i);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
//因为父类不是Thread,是Object,所以不能用super.getName
System.out.println(Thread.currentThread().getName() + "线程任务执行了" + i);
}
}
}
Callable接口set、get线程名
package com.itheima.thread_method;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadNameDemo3 {
/*
线程设置名字和获取名字
Thread类的方法:
public String getName() : 获取线程名字
public void setName() : 设置线程名字
public static Thread currentThread() : 获取当前线程的对象
*/
public static void main(String[] args) throws Exception {
MyCallable mc = new MyCallable();
FutureTask<Integer> task1 = new FutureTask<>(mc);
FutureTask<Integer> task2 = new FutureTask<>(mc);
Thread t1 = new Thread(task1, "线程A: ");
Thread t2 = new Thread(task2, "线程B: ");
t1.start();
t2.start();
Integer result1 = task1.get();
Integer result2 = task2.get();
//用线程对象获取名字
System.out.println(t1.getName() + "获取到的结果为:" + result1);
System.out.println(t2.getName() + "获取到的结果为:" + result2);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
线程休眠方法
package com.itheima.thread_method;
public class ThreadMethodDemo1 {
/*
休眠线程的方法
public static void sleep(long time) : 让线程休眠指定的时间,单位为毫秒
*/
public static void main(String[] args) throws InterruptedException {
for(int i = 5; i >= 1; i--){
System.out.println("倒计时:" + i + "秒");
// 项目经理要求此处代码运行缓慢, 让甲方加钱优化.
Thread.sleep(1000);
}
}
}
5、Thread类的常见方法2
线程调度方式:抢占式调度、非抢占式调度
线程优先级:1~10,默认为5,越大优先级越高
线程优先级越高抢占到CPU的概率也就越高,并不是必定抢到CPU的
线程优先级的方法
public setPriority(int newPriority) : 设置线程优先级
public final int getPriority() : 获取线程优先级
package com.itheima.thread_method;
public class ThreadMethodDemo2 {
/*
线程优先级的方法:
public setPriority(int newPriority) : 设置线程优先级
public final int getPriority() : 获取线程优先级
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(Thread.currentThread().getName() + "--- 线程任务执行" + i);
}
}
}, "线程A: ");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(Thread.currentThread().getName() + "--- 线程任务执行" + i);
}
}
}, "线程B: ");
t1.setPriority(1);
t2.setPriority(10);
// System.out.println(t1.getPriority());
// System.out.println(t2.getPriority());
t1.start();
t2.start();
}
}
守护线程
package com.itheima.thread_method;
public class ThreadMethodDemo3 {
/*
public final void setDaemon(boolean on) : 设置为守护线程
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
for(int i = 1; i <= 20; i++){
System.out.println(Thread.currentThread().getName() + "--- 任务执行了" + i);
}
}
}, "线程A");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
for(int i = 1; i <= 200; i++){
System.out.println(Thread.currentThread().getName() + "--- 任务执行了" + i);
}
}
}, "线程B");
t2.setDaemon(true);//当其他线程结束时,线程B也会结束
t1.start();
t2.start();
}
}
三、线程安全和同步
package com.itheima.test;
import java.util.ArrayList;
import java.util.List;
public class ThreadTest1 {
/*
需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
- 多条线程共享操作同一份资源
*/
public static void main(String[] args) {
TicketTask task = new TicketTask();//一个锁对象
/*List< String> threads = new ArrayList<>();
threads.add("窗口A");
threads.add("窗口B");
threads.add("窗口C");
threads.add("窗口D");
System.out.println(threads);*/
Thread t1 = new Thread(task, "窗口A");
Thread t2 = new Thread(task, "窗口B");
Thread t3 = new Thread(task, "窗口C");
t1.start();
t2.start();
t3.start();
}
}
class TicketTask implements Runnable {
int tickets = 1000;
@Override
public void run() {
while (true) {
synchronized (TicketTask.class) {//用字节码对象当锁对象
if (tickets <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + tickets + "号票");
tickets--;
}
}
}
}
1、同步方法
方法分为静态和非静态 静态方法的锁对象是字节码对象,非静态方法的锁对象是this
package com.itheima.test;
public class ThreadTest2 {
/*
同步方法: 在方法的返回值类型前面加入 synchronized 关键字
方法分为静态和非静态
静态方法的锁对象是字节码对象,非静态方法的锁对象是 this
*/
public static void main(String[] args) {
TicketTask2 task = new TicketTask2();
Thread t1 = new Thread(task, "窗口A");
Thread t2 = new Thread(task, "窗口B");
t1.start();
t2.start();
}
}
class TicketTask2 implements Runnable {
static int tickets = 1000;
@Override
public void run() {
while (true) {
String name = Thread.currentThread().getName();
if ("窗口A".equals(name)) {
if (method()) {
break;
}
} else if ("窗口B".equals(name)) {
synchronized (TicketTask2.class) {
if (tickets == 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + tickets + "号票");
tickets--;
}
}
}
}
private static synchronized boolean method() {
if (tickets == 0) {
return true;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + tickets + "号票");
tickets--;
return false;
}
}
2、Lock锁
package com.itheima.test;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest3 {
/*
使用 Lock 锁, 我们可以更清晰的看到哪里加了锁,哪里释放了锁
Lock 是接口, 无法直接创建对象, 需要创建 ReentrantLock
*/
public static void main(String[] args) {
TicketTask3 task = new TicketTask3();
Thread t1 = new Thread(task, "窗口A");
Thread t2 = new Thread(task, "窗口B");
t1.start();
t2.start();
}
}
class TicketTask3 implements Runnable {
int tickets = 1000;
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (tickets == 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + tickets + "号票");
tickets--;
} finally {//保证锁对象一定会被释放
lock.unlock();
}
}
}
}
四、线程池介绍
线程池介绍 系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互 当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程,就会严重浪费系统资源
将线程对象 交给线程池维护 可以降低系统成本 从而提升程序的性能 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为Integer.MAXVALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool : 允许的创建线程数量为Integer.MAXVALUE,可能会创建大量的线程,从而导致OOM。
五、JDK提供的线程池
JDK自带的线程池 (了解)
Executors 中提供静态方法来创建线程池
1. static ExecutorService newCachedThreadPool() 创建一个默认的线程池
2. static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
package com.itheima.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo1 {
/*
JDK自带的线程池 (了解)
Executors 中提供静态方法来创建线程池
1. static ExecutorService newCachedThreadPool() 创建一个默认的线程池
2. static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
*/
public static void main(String[] args) {
// 获取线程池对象
ExecutorService pool = Executors.newFixedThreadPool(10);
// 提交线程任务到线程池
for (int i = 1; i <= 100; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "提交了线程任务");
}
});
}
pool.shutdown();
}
}
六、自定义线程池
临时线程什么时候创建? 线程任务数>核心线程数+任务队列的数量 什么时候会开启拒绝策略 线程任务数>最大线程数+任务队列的数量
ThreadPoolExecutor(int corePoolsize,int maximumPoolsize, long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
/*
参数1 核心线程数量(正式员工)不能小于0
参数2 最大线程数量(正式员工+临时工)不能小于等于0,最大数量>=核心线程数量
参数3 空闲时间,不能小于0
参数4 时间单位
参数5 任务队列(指定排队人数),不能为null
参数6 线程对象工厂,不能为null
参数7 拒绝策略,不能为null
*/
拒绝策略:
最常用拒绝策略:
ThreadPoolExecutor.AbortPolicy
丢弃任务并抛出RejectedExecutionException异常(默认)
ThreadPoolExecutor.DiscardPolicy
丢弃任务,但是不抛出异常这是不推荐的做法
ThreadPoolExecutor.DiscardoldestPolicy
抛弃队列中等待最久的任务然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy
调用任务的run()方法(主方法),绕过线程池直接执行
自定义线程池
package com.itheima.pool;
import java.util.concurrent.*;
public class ThreadPoolDemo2 {
/*
自定义线程池
1. 有界队列 new ArrayBlockingQueue<>(10)
2. 无界队列 new LinkedBlockingQueue<>()
*/
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
for(int i = 1; i <= 16; i++){//线程数量>核心线程数量+任务队列数量时,开辟临时线程
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--- 提交了任务");
}
});
}
}
}
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)