先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
}
下面是acquire() 的实现:
public final void acquire(int arg) {
//tryAcquire() 再次尝试获取锁,
//如果发现锁就是当前线程占用的,则更新state,表示重复占用的次数,
//同时宣布获得所成功,这正是重入的关键所在
if (!tryAcquire(arg) &&
// 如果获取失败,那么就在这里入队等待
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果在等待过程中 被中断了,那么重新把中断标志位设置上
selfInterrupt();
}
公平锁和非公平锁区别
//非公平锁
final void lock() {
//上来不管三七二十一,直接抢了再说
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//抢不到,就进队列慢慢等着
acquire(1);
}
//公平锁
final void lock() {
//直接进队列等着
acquire(1);
}
非公平锁如果第一次争抢失败,后面的处理和公平锁是一样的,都是进入等待队列慢慢等。
3.Condition
概述
Condition接口可以理解为重入锁的伴生对象。它提供了在重入锁的基础上,进行等待和通知的机制。可以使用 newCondition()方法生成一个Condition对象。
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
应用场景
在ArrayBlockingQueue阻塞队列中,就维护一个Condition对象
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
这个notEmpty 就是一个Condition对象。它用来通知其他线程,ArrayBlockingQueue是不是空着的。当我们需要拿出一个元素时:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
// 如果队列长度为0,那么就在notEmpty condition上等待了,一直等到有元素进来为止
// 注意,await()方法,一定是要先获得condition伴生的那个lock,才能用的哦
notEmpty.await();
//一旦有人通知我队列里有东西了,我就弹出一个返回
return dequeue();
} finally {
lock.unlock();
}
}
当有元素入队时:
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
//先拿到锁,拿到锁才能操作对应的Condition对象
lock.lock();
try {
if (count == items.length)
return false;
else {
//入队了, 在这个函数里,就会进行notEmpty的通知,通知相关线程,有数据准备好了
enqueue(e);
return true;
}
} finally {
//释放锁了,等着的那个线程,现在可以去弹出一个元素试试了
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
//元素已经放好了,通知那个等着拿东西的人吧
notEmpty.signal();
}
4.LockSupport类
LockSupport可以理解为一个工具类。它的作用很简单,就是挂起和继续执行线程。它的常用的API如下:
public static void park() : 如果没有可用许可,则挂起当前线程
public static void unpark(Thread thread):给thread一个可用的许可,让它得以继续执行
5.AbstractQueuedSynchronizer内部数据结构
在AbstractQueuedSynchronizer内部,有一个队列,我们把它叫做同步等待队列。它的作用是保存等待在这个锁上的线程(由于lock()操作引起的等待)。此外,为了维护等待在条件变量上的等待线程,AbstractQueuedSynchronizer又需要再维护一个条件变量等待队列,也就是那些由Condition.await()引起阻塞的线程。
可以看到,无论是同步等待队列,还是条件变量等待队列,都使用同一个Node类作为链表的节点。
6.AQS对资源的共享方式
AQS底层使用了模板方法模式
1.互斥锁和自旋锁
2.乐观锁和悲观锁你的理解,使用场景
乐观锁
概念
先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。
使用场景
在线文档
我们都知道在线文档可以同时多人编辑的,如果使用了悲观锁,那么只要有一个用户正在编辑文档,此时其他用户就无法打开相同的文档了,这用户体验当然不好了。
SVN和Git
先让用户编辑代码,然后提交的时候,通过版本号来判断是否产生了冲突,发生了冲突的地方,需要我们自己修改后,再重新提交。
悲观锁
概念
认为多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁。
使用场景
互斥锁、自旋锁、读写锁,都是属于悲观锁。
3.读锁和写锁
4.可重入锁
1.什么是线程安全
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其它的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
2.如何创建线程
1)继承Thread类
优势:编程比较简单,可以直接使用Thread类中的方法。
劣势:可扩展性较差,不能再继承其他的类。
2)实现Runnable接口
优势:扩展性强,实现该接口的同时还可以继承其他的类。
劣势:编程相对复杂,不能直接使用Thread类中的方法。
3)实现Callable接口
Callable 执行的任务有返回值,而 Runnable 执行的任务没有返回值。可以通过FutureTask中的get方法获取返回值。
Callable(重写)的方法是 call 方法,而 Runnable(重写)的方法是 run 方法。
call 方法可以抛出异常,而 Runnable 方法不能抛出异常。
4)使用线程池
3.线程间的通信方式
volatile关键字方式
利用volatile保证可见性,使得其他线程感受到共享变量的变化
等待/通知机制
基于Object类中的wait和notify方法
join方式
当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行。
threadLocal方式
4.wait和notify的底层实现
应用层面
jvm层面
前置工作
1)进入wait/notify方法之前,要获取synchronized锁
2)synchronized生成的字节码指令有monitorenter和 monitorexit,执行monitorenter指令可以获取对象的monitor
3)线程执行lock.wait()方法时,必须持有该lock对象的monitor,如果wait方法在synchronized代码中执行,该线程很显然已经持有了monitor。
4)在HotSpot虚拟机中,monitor采用ObjectMonitor实现。
5)ObjectMonitor对象中有两个队列:_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表;_owner指向获得ObjectMonitor对象的线程。
_WaitSet :处于wait状态的线程,会被加入到wait set;
_EntryList:处于等待锁block状态的线程,会被加入到entry set;
6)ObjectWaiter对象是双向链表结构,保存了_thread(当前线程)以及当前的状态TState等数据, 每个等待锁的线程都会被封装成ObjectWaiter对象。
wait方法实现
lock.wait()方法最终通过ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);实现:
1)将当前线程封装成ObjectWaiter对象node;
2)通过ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中;
3)通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象。
4)最终底层的park方法会挂起线程;
notify方法实现
lock.notify()方法最终通过ObjectMonitor的void notify(TRAPS)实现:
1)如果当前_WaitSet为空,即没有正在等待的线程,则直接返回;
2)通过ObjectMonitor::DequeueWaiter方法,获取_WaitSet列表中的第一个ObjectWaiter节点,实现也很简单。
这里需要注意的是,在jdk的notify方法注释是随机唤醒一个线程,其实是第一个ObjectWaiter节点
3)根据不同的策略,将取出来的ObjectWaiter节点,加入到_EntryList或则通过
5.线程切换方式
6.程序一般开多少线程
7.notify和notifyAll区别
8.wait和sleep的区别
1.线程池优点
降低资源消耗
重用存在的线程,减少对象创建销毁的开销。
提高响应速度
可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性
线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
附加功能
提供定时执行、定期执行、单线程、并发数控制等功能。
2.线程池有哪几种
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
3.线程池参数
corePoolSize: 线程池核心线程数最大值
maximumPoolSize: 线程池最大线程数大小
keepAliveTime: 线程池中非核心线程空闲的存活时间大小
unit: 线程空闲存活时间单位
workQueue: 存放任务的阻塞队列
threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。
handler: 线城池的拒绝策略事件,主要有四种类型。
4.线程池的拒绝策略
5.执行execute()方法和submit()方法的区别
6.线程池工作原理
1.ConCurrentHashMap
出现背景
1)HashMap线程不安全,在1.7中采用头插法会造成死循环,在1.8中改为尾插法,会造成元素覆盖。
2)HashTable集合类和Collections下的SynchronizedMap类是线程安全的,但是会锁住整个表,效率低下。
jdk1.7和1.8中采用的技术
1)jdk1.7中ConcurrentHashMap采用锁分段技术,每个部分是一个Segment。
2)jdk1.8中采用Synchronized + CAS,把锁的粒度进一步降低
jdk1.7的底层原理
存储结构
1)采用链表加数组的数据结构,把原来的整个table划分为n个 Segment 。每个 Segment 里边是由 HashEntry 组成的数组,每个 HashEntry之间又可以形成链表。
2)当对某个 Segment 加锁时,并不会影响到其他 Segment 的读写。
put方法的流程
1)通过哈希算法计算出当前 key 的 hash 值
2)通过这个hash值找到它所对应的Segment数组的下标
3)再通过hash值计算出它在对应Segment的HashEntry数组 的下标
4)找到合适的位置插入元素
size方法底层实现
首先采用乐观的方式,认为统计 size 的过程中,并没有发生 put, remove 等会改变 Segment 结构的操作。遍历统计count和modcount的个数,其中count指的是每个Segment元素的个数,modcount指的是每次 table 结构修改时,如put,remove等,此变量都会自增。如果发生了修改,则需要重试,重试两次都不成功,则需要把所有segment加锁之后再计算。
jdk1.8底层原理
存储结构
数组+链表+红黑树,不再有Segment的概念,而是给数组中的每一个头节点(桶)都加锁,使用的是Synchronized 锁。在jdk1.6之后,Synchronized引入了锁升级的概念。
put方法
若当前桶为空,则通过 CAS 原子操作,把新节点插入到此位置
2.AtomicInteger类的原理
3.CountDownLatch
4.CyclicBarrier
一组线程会互相等待,直到所有线程都到达一个同步点。这个就非常有意思了,就像一群人被困到了一个栅栏前面,只有等最后一个人到达之后,他们才可以合力把栅栏(屏障)突破。
5.Semaphore
1.作用
2.应用场景
3.底层原理
1.手写死锁
package com.shenhao;
public class Demo {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();
new Thread(() -> {
while(true){
synchronized (objA){
//线程一
synchronized (objB) {
System.out.println(“小A正在走路”);
}
}
}
}).start();
new Thread(() -> {
while(true){
synchronized (objB){
//线程二
synchronized (objA) {
System.out.println(“小B正在走路”);
}
}
}
}).start();
}
}
2.手写生产者消费者
生产者
package com.shenhao.threaddemo16;
import java.util.concurrent.ArrayBlockingQueue;
public class Producer extends Thread{
private ArrayBlockingQueue abq;
public Producer(ArrayBlockingQueue abq){
this.abq = abq;
}
@Override
public void run() {
while(true){
try {
abq.put(“用品”);
System.out.println(“生产者生产一个用品”);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者
package com.shenhao.threaddemo16;
import java.util.concurrent.ArrayBlockingQueue;
public class Consumer extends Thread{
private ArrayBlockingQueue abq;
public Consumer(ArrayBlockingQueue abq){
this.abq = abq;
}
@Override
public void run() {
while(true){
String s = null;
try {
s = abq.take();
System.out.println(“消费者消费” + s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
主程序调用
package com.shenhao.threaddemo16;
import java.util.concurrent.ArrayBlockingQueue;
public class Demo {
public static void main(String[] args) {
//阻塞队列,容量为1
ArrayBlockingQueue abq = new ArrayBlockingQueue<>(1);
Producer p = new Producer(abq);
Consumer c = new Consumer(abq);
p.start();
c.start();
}
}
3.手写阻塞队列
阻塞队列
最后
光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性
Java面试宝典2021版
最常见Java面试题解析(2021最新版)
2021企业Java面试题精选
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
out.println(“消费者消费” + s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
主程序调用
package com.shenhao.threaddemo16;
import java.util.concurrent.ArrayBlockingQueue;
public class Demo {
public static void main(String[] args) {
//阻塞队列,容量为1
ArrayBlockingQueue abq = new ArrayBlockingQueue<>(1);
Producer p = new Producer(abq);
Consumer c = new Consumer(abq);
p.start();
c.start();
}
}
3.手写阻塞队列
阻塞队列
最后
光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性
Java面试宝典2021版
[外链图片转存中…(img-smIzwXKA-1713547961847)]
[外链图片转存中…(img-FeYZj7wR-1713547961848)]
最常见Java面试题解析(2021最新版)
[外链图片转存中…(img-JC7YxIY0-1713547961848)]
[外链图片转存中…(img-BU6L3Vem-1713547961849)]
2021企业Java面试题精选
[外链图片转存中…(img-BWrLdEWt-1713547961849)]
[外链图片转存中…(img-lZhFtZHs-1713547961849)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-RNn9oPBZ-1713547961849)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!