看之前先准备三桶水,我怕你渴死!!!!史上Java面试最全攻略,让你拥有博尔特一般的起步!!!!
写在前面
说到Java并发编程,相信大家都不会陌生。并发编程是Java程序员重要的技能之一,也是难掌握的一种技能。它要求编程者对计算机底层的运作原理有深刻的理解,同时要求编程者逻辑清晰、思维缜密,这样才能写出高效、安全、可靠的多线程并发程序
在面试和实际工作中并发编程更是和我们息息相关,今天为大家带来一份华为工程师撰写的Java面试实战手稿,你花点耐心看完会让你对并发编程有不一样的认知。
关于并发编程实战
我准备从以下几个点逐一带大家了解→认知→战并发编程
- 并发编程的原理
- Java并发机制的底层实现原理
- Java内存模型
- Java并发编程基础
- Java中的锁
- Java并发容器和框架
- Java中的13个原子操作类
- Java中的并发工具类
- Java中的线程池
- Executor框架
- Java并发编程实践
完整版的Java并发编程实战文档我已经帮大家整理好了。需要的同学直接看文末卡片即可!!!
并发编程的原理
并发编程的目的是为了让程序运行得更快,但是,并不是启动更多的线程就能让程序最快 大限度地并发执行。在进行并发编程时,如果希望通过多线程执行任务让程序运行得更快,会 面临非常多的挑战,比如上下文切换的问题、死锁的问题,以及受限于硬件和软件的资源限制 问题
即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现 这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切 换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个 任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这 个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
Java并发机制的底层实现原理
Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节 码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和 CPU的指令。本章我们将深入底层一起探索下Java并发机制的底层实现原理。
volatile的应用
在多线程并发编程中synchronized和volatile都扮演着重要的角色,volatile是轻量级的 synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程 修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当 的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。本 文将深入分析在硬件层面上Intel处理器是如何实现volatile的,通过深入分析帮助我们正确地 使用volatile变量。
关于Java内存模型
Java线程之间的通信对程序员完全透明,内存可见性问题很容易困扰Java程序员,本章将 揭开Java内存模型神秘的面纱。本章大致分4部分:Java内存模型的基础,主要介绍内存模型相 关的基本概念;Java内存模型中的顺序一致性,主要介绍重排序与顺序一致性内存模型;同步 原语,主要介绍3个同步原语(synchronized、volatile和final)的内存语义及重排序规则在处理器 中的实现;Java内存模型的设计,主要介绍Java内存模型的设计原理,及其与处理器内存模型 和顺序一致性内存模型的关系。
并发编程模型的两个关键问题
在并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的 线程是指并发执行的活动实体)。通信是指线程之间以何种机制来交换信息。在命令式编程 中,线程之间的通信机制有两种:共享内存和消息传递。
在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态 进行隐式通信。在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息 息来显式进行通信。
同步是指程序中用于控制不同线程间操作发生相对顺序的机制。共享内存并发模型 里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。 在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。
Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对 程序员完全透明。如果编写多线程程序的Java程序员不理解隐式进行的线程之间通信的工作 机制,很可能会遇到各种奇怪的内存可见性问题。
Java中的锁
锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时攻击 访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。在Lock接 口出现之前,Java程序是靠synchronized关键字实现锁功能的,而Java SE 5分钟之后,并发包中新增 了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功 能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提 供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断地获取锁以及 及超时获取锁等多种synchronized关键字所不具备的同步特性。
使用synchronized关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先 获取再释放。当然,这种方式简化了同步的管理,可是扩展性没有显示的所获取和释放来的 好。例如,针对一个场景,手把手进行锁获取和释放,先获得锁A,然后再获取锁B,当锁B获得 后,释放锁A同时获取锁C,当锁C获得后,再释放B同时获取锁D,以此类推。这种场景下, synchronized关键字就不那么容易实现了,而使用Lock却容易许多。
Java中的线程池
Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序 都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源, 还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用 线程池,必须对其实现原理了如指掌。
线程池的实现原理
1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作 线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这 个工作队列里。如果工作队列满了,则进入下个流程。
3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程 来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
内容截图:
最后
关于并发编程,我们需要了解和掌握的远远不止以上这些皮毛(由于工作时间原因,小编只有空写这么多了) ,完整的Java并发编程手稿和笔记我已经帮大家整理好了,需要的同学点击文末卡片即可到手资料!!!