当前位置: 首页 > news >正文

秋招Java后端开发冲刺——并发篇1(线程与进程、多线程)

一、进程

1. 进程
进程是程序的一次动态执行过程,是操作系统资源分配的基本单位。
2. 进程和线程的区别

特性进程线程
定义独立运行的程序实例,资源分配的基本单位进程中的一个执行单元,CPU调度的基本单位
资源进程拥有独立的内存空间和资源线程共享进程的堆和方法区(JDK1.8之后为元空间)
通信进程间通信(IPC)较为复杂线程间通信(共享内存)较为简单
开销创建和销毁进程开销较大创建和销毁线程开销较小
独立性进程之间相对独立线程间相互影响
并发性进程可以并发执行线程可以并发执行
调度由操作系统调度由操作系统或线程库调度
崩溃影响一个进程崩溃不会影响其他进程一个线程崩溃可能会影响整个进程

3. 进程和线程的联系

联系描述
组成关系一个进程可以包含一个或多个线程,线程是进程的一部分,多个线程共享进程的资源。
资源共享线程共享进程的堆和方法区(JDK1.8之后为元空间)、文件句柄等资源,进程则有自己的独立资源。
并发执行进程和线程都可以并发执行,利用多核 CPU 提高程序的并行度。
调度进程和线程都由操作系统进行调度,多线程程序中,线程的调度可以由 JVM 和操作系统共同管理。

二、线程

1. Java线程

  • JDK 1.2 之前,Java 线程是基于绿色线程(Green Threads)实现的,这是一种用户级线程(用户线程);JDK1.2之后Java线程是基于原生线程(Native Threads,操作系统内核线程)实现。
  • 虚拟机栈和本地方法栈线程私有是为了保证局部变量不被其他线程访问
  • 程序技术器线程私有是为了线程切换后能找到上次运行的位置继续执行

2. 线程的创建方式

  • 继承Thread类并重写 run 方法来定义线程的执行逻辑。
public class MyThread extends Thread {@Overridepublic void run() {System.out.println("Thread is running");}public static void main(String[] args) {MyThread thread = new MyThread();thread.start();  // 启动线程}
}
  • 实现Runable接口并将其实例传递给 Thread 对象
public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Runnable is running");}public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start();  // 启动线程}
}
  • 实现Callable接口并使用 FutureTask 包装 Callable 对象,然后将其传递给 Thread 对象(Callable 可以有返回值,且可以抛出异常
public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return "Callable is running";}public static void main(String[] args) {MyCallable callable = new MyCallable();FutureTask<String> futureTask = new FutureTask<>(callable);Thread thread = new Thread(futureTask);thread.start();  // 启动线程try {// 获取执行结果String result = futureTask.get();System.out.println(result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}
}
  • 其他方式:使用线程池、使用CompletableFuture类

3. 线程(Thread类)的常见方法

方法名描述
void start()启动线程,调用线程的 run 方法
void run()线程的执行方法,需要重写
void interrupt()中断线程
boolean isInterrupted()测试线程是否已经中断
static boolean interrupted()测试当前线程是否已经中断,并清除当前线程的中断状态
void join()等待线程终止
void join(long millis)等待线程终止最长时间为 millis 毫秒
void join(long millis, int nanos)等待线程终止最长时间为 millis 毫秒加 nanos 纳秒
static void sleep(long millis)使当前线程睡眠(暂停执行)指定的毫秒数
static void sleep(long millis, int nanos)使当前线程睡眠(暂停执行)指定的毫秒数加纳秒数
void setPriority(int newPriority)更改线程的优先级
int getPriority()返回线程的优先级
void setName(String name)更改线程名称
String getName()返回线程名称
long getId()返回线程的唯一标识符
Thread.State getState()返回线程的状态
boolean isAlive()测试线程是否还活着
static void yield()暂停当前正在执行的线程对象,并执行其他线程
static Thread currentThread()返回对当前正在执行的线程对象的引用
static int activeCount()返回当前线程的线程组中活动线程的数目
static void dumpStack()将当前线程的堆栈跟踪打印到标准错误流
StackTraceElement[] getStackTrace()返回一个数组,表示该线程的堆栈转储
static boolean holdsLock(Object obj)当且仅当当前线程在指定的对象上保持监视器锁时,返回 true
void setDaemon(boolean on)将该线程标记为守护线程或用户线程
boolean isDaemon()测试该线程是否为守护线程
void checkAccess()判断当前运行的线程是否有权限修改此线程
ThreadGroup getThreadGroup()返回该线程所属的线程组

4. 线程的生命周期

  • New (新建状态):线程对象被创建,但还未调用 start() 方法。
  • Runnable (就绪状态):start() 方法被调用,线程进入就绪状态,等待 CPU 时间片的分配。
  • Running (运行状态):线程获得 CPU 时间片,开始执行 run() 方法中的代码。
  • Blocked (阻塞状态):线程因等待资源或锁而进入阻塞状态,无法继续执行。
  • Waiting (等待状态):线程等待另一个线程显式地唤醒自己,通过 wait()、join() 或 sleep() 等方法进入等待状态。
  • Timed Waiting (计时等待状态):线程等待一定时间后会被自动唤醒,通过 sleep(long millis)、wait(long timeout) 或 join(long millis) 等方法进入计时等待状态。
  • Terminated (终止状态):线程运行结束或因异常退出 run() 方法,线程进入终止状态
    以下是线程生命周期的图解:
    (1)JDK1.5之前

    (2)JDK1.5之后
    在这里插入图片描述

三、多线程

1. 线程安全问题的解决方式

(1)产生原因

  • 共享资源:多个线程同时访问和修改同一资源,例如变量、对象、文件等。
  • 缺乏同步:线程在访问共享资源时,没有正确使用同步机制,导致多个线程同时执行对共享资源的操作。
  • 原子性操作的缺乏:对共享资源的操作需要分多个步骤完成,如果这些步骤不能保证原子性,会导致线程安全问题。
  • 可见性问题:一个线程对共享资源的修改,其他线程不能立即看到,导致数据不一致。
  • 指令重排序:编译器和处理器为了优化性能,可能会对指令进行重排序,导致线程安全问题

(2)解决方式

  • Synchronized关键字:同步块可以确保在同一时间只有一个线程执行同步代码,从而避免多个线程同时访问共享资源的问题。(详解请参考)
public class SynchronizedExample {public synchronized void synchronizedMethod() {// 同步实例方法// 其他线程不能同时执行此方法}public static synchronized void staticSynchronizedMethod() {// 同步静态方法// 其他线程不能同时执行此静态方法}public void synchronizedBlock() {synchronized (this) {// 同步代码块// 锁定当前实例对象}}
}
  • Lock锁:是 Java 提供的一种显式锁机制,有多种锁类型实现。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockExample {private final Lock lock = new ReentrantLock();public void lockMethod() {lock.lock();try {// 临界区代码// 其他线程不能同时执行此代码} finally {lock.unlock();}}
}
  • 使用线程本地变量 (ThreadLocal):每个线程都有自己的变量副本,互不干扰。
public class ThreadLocalExample {private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);public void increment() {threadLocal.set(threadLocal.get() + 1);}public int get() {return threadLocal.get();}
}
  • Atomic Variables(原子变量):java.util.concurrent.atomic 包提供了多种原子变量,如 AtomicInteger、AtomicLong、AtomicReference 等,它们提供了一种无锁的线程安全机制。
import java.util.concurrent.atomic.AtomicInteger;public class AtomicExample {private final AtomicInteger counter = new AtomicInteger(0);public void increment() {counter.incrementAndGet();}public int getCounter() {return counter.get();}
}

(3) Synchronized 和 Lock 的区别

特性synchronizedLock
实现内置语言特性通过 java.util.concurrent.locks 包提供
锁的释放自动释放:线程退出同步代码块或方法时自动释放需要显式调用 unlock() 方法
灵活性灵活性较低,只能锁定方法或代码块灵活性较高,可以尝试获取锁、定时获取锁等
锁的获取线程阻塞式等待支持阻塞式、非阻塞式、定时尝试获取锁
性能较低:适用于简单的同步较高:适用于复杂的并发控制
条件变量提供 Condition 类,支持多个条件变量
中断响应不支持线程中断支持线程中断,响应中断请求
读写锁不支持支持,通过 ReentrantReadWriteLock 实现
公平锁不支持支持公平锁,通过 ReentrantLock 实现

2. 死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。

(1)死锁的产生:四个必要条件

  • 非抢占式:线程已获得的资源在未使用完之前不能被其他线程强行剥夺
  • 循环等待:若干线程之间形成一种头尾相接的循环等待资源关系
  • 互斥条件:该资源任意一个时刻只由一个线程占用
  • 请求与保持条件:个线程因请求资源而阻塞时,对已获得的资源保持不放

(2)死锁的预防:破坏必要条件

  • 破坏请求与保持条件:一次性申请所有的资源(会造成内存开销极大,因为程序可能很长一段时间使用不到该资源)。
  • 破坏非抢占式条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  • 破坏循环等待条件:按某一顺序申请资源,释放资源则反序释放。

(3)死锁的避免

  • 在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。
  • 安全状态:系统能够按照某种线程推进顺序(P1、P2、P3……Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。
  • 银行家算法代码实现
import java.util.Arrays;public class BankersAlgorithm {private int numProcesses;private int numResources;private int[] available;private int[][] maximum;private int[][] allocation;private int[][] need;public BankersAlgorithm(int numProcesses, int numResources) {this.numProcesses = numProcesses;this.numResources = numResources;this.available = new int[numResources];this.maximum = new int[numProcesses][numResources];this.allocation = new int[numProcesses][numResources];this.need = new int[numProcesses][numResources];}public void setAvailable(int[] available) {System.arraycopy(available, 0, this.available, 0, numResources);}public void setMaximum(int process, int[] max) {System.arraycopy(max, 0, this.maximum[process], 0, numResources);for (int j = 0; j < numResources; j++) {this.need[process][j] = this.maximum[process][j] - this.allocation[process][j];}}public void setAllocation(int process, int[] alloc) {System.arraycopy(alloc, 0, this.allocation[process], 0, numResources);for (int j = 0; j < numResources; j++) {this.need[process][j] = this.maximum[process][j] - this.allocation[process][j];}}public boolean requestResources(int process, int[] request) {// Step 1: Check if request <= needfor (int j = 0; j < numResources; j++) {if (request[j] > need[process][j]) {return false; // Request exceeds need}}// Step 2: Check if request <= availablefor (int j = 0; j < numResources; j++) {if (request[j] > available[j]) {return false; // Request exceeds available resources}}// Step 3: Pretend to allocate requested resourcesfor (int j = 0; j < numResources; j++) {available[j] -= request[j];allocation[process][j] += request[j];need[process][j] -= request[j];}// Step 4: Check system safetyif (checkSafety()) {return true; // Safe state, allocation is successful} else {// Revert allocation if not safefor (int j = 0; j < numResources; j++) {available[j] += request[j];allocation[process][j] -= request[j];need[process][j] += request[j];}return false; // Not a safe state}}private boolean checkSafety() {boolean[] finish = new boolean[numProcesses];int[] work = Arrays.copyOf(available, numResources);while (true) {boolean foundProcess = false;for (int i = 0; i < numProcesses; i++) {if (!finish[i]) {boolean canAllocate = true;for (int j = 0; j < numResources; j++) {if (need[i][j] > work[j]) {canAllocate = false;break;}}if (canAllocate) {for (int j = 0; j < numResources; j++) {work[j] += allocation[i][j];}finish[i] = true;foundProcess = true;}}}if (!foundProcess) {break;}}for (boolean f : finish) {if (!f) {return false; // System is not in a safe state}}return true; // System is in a safe state}public static void main(String[] args) {int numProcesses = 5;int numResources = 3;BankersAlgorithm ba = new BankersAlgorithm(numProcesses, numResources);ba.setAvailable(new int[]{10, 5, 7});ba.setMaximum(0, new int[]{7, 5, 3});ba.setMaximum(1, new int[]{3, 2, 2});ba.setMaximum(2, new int[]{9, 0, 2});ba.setMaximum(3, new int[]{2, 2, 2});ba.setMaximum(4, new int[]{4, 3, 3});ba.setAllocation(0, new int[]{0, 1, 0});ba.setAllocation(1, new int[]{2, 0, 0});ba.setAllocation(2, new int[]{3, 0, 2});ba.setAllocation(3, new int[]{2, 1, 1});ba.setAllocation(4, new int[]{0, 0, 2});int[] request = {1, 0, 2};int process = 1;boolean success = ba.requestResources(process, request);System.out.println("Request " + (success ? "granted" : "denied"));}
}

3. 线程池
(1)核心参数

  • 核心线程数 (corePoolSize):线程池中保持活动的最小线程数量,即使这些线程处于空闲状态。
  • 最大线程数 (maximumPoolSize):线程池中允许的最大线程数量。当任务队列已满且已达到核心线程数时,线程池会创建新的线程来处理任务,直到达到最大线程数。达到最大线程数后,新任务将被拒绝处理,并根据饱和策略进行处理。
  • 空闲线程存活时间 (keepAliveTime):当线程池中线程数量超过核心线程数时,多余的空闲线程在等待新任务时的最长存活时间。超过这个时间的空闲线程将被终止和移除,直到线程池中的线程数量等于核心线程数。
  • 时间单位 (unit):空闲线程存活时间的单位,如秒、毫秒等。与 keepAliveTime 参数一起使用。
  • 任务队列 (workQueue):用于保存等待执行任务的阻塞队列。常用的队列实现有:
    • 直接提交队列 (SynchronousQueue):不保存任务,每个插入操作必须等待相应的删除操作。
    • 有界队列 (ArrayBlockingQueue):有固定容量的队列,当队列满时,插入操作将被阻塞。
    • 无界队列 (LinkedBlockingQueue):队列大小没有上限,理论上可以无限制地增加队列长度。
    • 优先队列 (PriorityBlockingQueue):按任务优先级排序的无界队列。
  • 线程工厂 (threadFactory):用于创建新线程的工厂。通过自定义线程工厂,可以为每个新线程设置名称、优先级等属性。
  • 拒绝策略 (handler):当任务队列已满且线程池中的线程数量已达到最大线程数时,如何处理新任务。Java 提供了四种预定义的拒绝策略:
    • AbortPolicy(默认):抛出 -
    • RejectedExecutionException,拒绝任务。
    • CallerRunsPolicy:由调用线程处理该任务。
    • DiscardPolicy:丢弃无法处理的任务,不予处理。
    • DiscardOldestPolicy:丢弃最早添加到队列中的任务,然后尝试重新提交新任务。

(2)线程池创建

  • 固定大小的线程池 (Fixed Thread Pool)
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
  • 单线程化的线程池 (Single Thread Executor)
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
  • 缓存的线程池 (Cached Thread Pool)
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
  • 定时任务线程池 (Scheduled Thread Pool)
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);

相关文章:

  • 半年支出8700万美元,财库预算只够再撑2年,波卡之后想咋过?
  • Linux kernel 与 设备树
  • Kubernetes (K8s) 底层原理
  • 一个强大的分布式锁框架——Lock4j
  • 定位线上同步锁仍然重复扣费的Bug定位及Redis分布式锁解决方案
  • JDK 为什么需要配置环境变量
  • 单片机IO
  • 项目实战--Spring Boot + Minio文件切片上传下载
  • PyTorch(六)优化模型参数
  • 2.2.5 C#中显示控件BDPictureBox 的实现----ROI交互续2
  • Golang中defer和return顺序
  • LabVIEW幅频特性测试系统
  • 前端工程化08-新的包管理工具pnpm
  • python系列30:各种爬虫技术总结
  • MySQL增删改查
  • 9月CHINA-PUB-OPENDAY技术沙龙——IPHONE
  • 「面试题」如何实现一个圣杯布局?
  • 【知识碎片】第三方登录弹窗效果
  • 30秒的PHP代码片段(1)数组 - Array
  • extract-text-webpack-plugin用法
  • flutter的key在widget list的作用以及必要性
  • leetcode98. Validate Binary Search Tree
  • Otto开发初探——微服务依赖管理新利器
  • TypeScript实现数据结构(一)栈,队列,链表
  • Web标准制定过程
  • windows下如何用phpstorm同步测试服务器
  • -- 查询加强-- 使用如何where子句进行筛选,% _ like的使用
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 函数式编程与面向对象编程[4]:Scala的类型关联Type Alias
  • 汉诺塔算法
  • 计算机常识 - 收藏集 - 掘金
  • 马上搞懂 GeoJSON
  • 区块链共识机制优缺点对比都是什么
  • 驱动程序原理
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • 远离DoS攻击 Windows Server 2016发布DNS政策
  • 正则表达式小结
  • 智能合约Solidity教程-事件和日志(一)
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​什么是bug?bug的源头在哪里?
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (九)信息融合方式简介
  • (六)c52学习之旅-独立按键
  • (三)elasticsearch 源码之启动流程分析
  • (四)汇编语言——简单程序
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (转)Linux整合apache和tomcat构建Web服务器
  • (转)平衡树
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .htaccess配置常用技巧
  • .Mobi域名介绍