先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
保证线程安全有以下几种方式:
-
Synchronized 关键字:被 Synchronized 关键字描述的方法或代码块在多线程环境下同一时间只能由一个线程进行访问,在持有当前 Monitor 的线程执行完成之前,其他线程想要调用相关方法就必须进行排队,知道持有持有当前 Monitor 的线程执行结束,释放 Monitor ,下一个线程才可获取 Monitor 执行。
-
Volatile 关键字:被 Volatile 关键字描述变量的操作具有可见性和有序性(禁止指令重排)
-
java.util.concurrent.atomic原子操作:ava.util.concurrent.atomic 包提供了一系列的 AtomicBoolean、AtomicInteger、AtomicLong 等类。使用这些类来声明变量可以保证对其操作具有原子性来保证线程安全。
-
Lock:Lock 也是 java.util.concurrent 包下的一个接口,定义了一系列的锁操作方法。Lock 接口主要有 ReentrantLock,ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock 实现类。与 Synchronized 不同是 Lock 提供了获取锁和释放锁等相关接口,使得使用上更加灵活,同时也可以做更加复杂的操作。
=========================================================================
通常,会使用线程池来管理线程。
在 JDK 1.5 之后推出了相关的 api,常见的创建线程池方式有以下几种:
-
Executors.newCachedThreadPool():无限线程池。
-
Executors.newFixedThreadPool(nThreads):创建固定大小的线程池。
-
Executors.newSingleThreadExecutor():创建单个线程的线程池。
其实看这三种方式创建的源码就会发现:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
实际上还是利用 ThreadPoolExecutor 类实现的。
通常我们都是使用:
threadPool.execute(new Job());
这样的方式来提交一个任务到线程池中,所以核心的逻辑就是 execute() 函数了。
线程池一共有五种状态, 分别是:
-
RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
-
SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
-
STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
-
TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
-
TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
进入TERMINATED的条件如下:
-
- 线程池不是RUNNING状态;
-
- 线程池状态不是TIDYING状态或TERMINATED状态;
-
- 如果线程池状态是SHUTDOWN并且workerQueue为空;
-
- workerCount为0;
-
- 设置TIDYING状态成功。
下图为线程池的状态转换过程:
再看看Excute方法的执行:
1、获取当前线程池的状态。
2、当前线程数量小于 coreSize 时创建一个新的线程运行。
3、如果当前线程处于运行状态,并且写入阻塞队列成功。
4、双重检查,再次获取线程状态;如果线程状态变了(非运行状态)就需要从阻塞队列移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
5、如果当前线程池为空就新创建一个线程并执行。
6、如果在第三步的判断为非运行状态,尝试新建线程,如果失败则执行拒绝策略。
当前SpringBoot比较流行,我们可以发挥Spring的特性,由Spring来替我们管理线程:
@Configuration
public class TreadPoolConfig {
/**
-
消费队列线程
-
@return
*/
@Bean(value = “consumerQueueThreadPool”)
public ExecutorService buildConsumerQueueThreadPool(){
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat(“consumer-queue-thread-%d”).build();
ExecutorService pool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue(5),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy());
return pool ;
}
}
使用时:
@Resource(name = “consumerQueueThreadPool”)
private ExecutorService consumerQueueThreadPool;
@Override
public void execute() {
//消费队列
for (int i = 0; i < 5; i++) {
consumerQueueThreadPool.execute(new ConsumerQueueThread());
}
}
其实也挺简单,就是创建了一个线程池的 bean,在使用时直接从 Spring 中取出即可。
假如有一个List,其中存的是用户User对象,用户对象有很多属性,我要根据其中的年龄属性对List排序,这个该怎么办?
==============================================================================================================================
可以通过Collections类的sort方法。但需要注意,使用sort方法的时候:
- 要么 User类实现Comparable接口,并在类中编写public int compareTo(T o)方法
public class User implements Comparable {
private int age;
private String name;
private String sex;
@Override
public int compareTo(User o) {
if (this.getAge() > o.getAge()) {
return 1;
} else if (this.getAge() < o.getAge()) {
return -1;
} else {
return 0;
}
}
// ……
}
List userList=new ArrayList();
userList.add(new User(10, “王二”, “男”));
userList.add(new User(8, “张三”, “男”));
userList.add(new User(17, “李四”, “女”));
Collections.sort(userList);
System.out.println(userList);
- 或者在排序的时候,给sort()方法传入一个比较器。具体来说,就是传入一个实现比较器接口的匿名内部类。
List userList=new ArrayList();
userList.add(new User(10, “王二”, “男”));
userList.add(new User(8, “张三”, “男”));
userList.add(new User(17, “李四”, “女”));
//Collections.sort(userList);
Collections.sort(userList, new Comparator() {
@Override
public int compare(User o1,User o2) {
if(o1.getAge()>o2.getAge()) {
return 1;
}else if(o1.getAge()<o2.getAge()) {
return -1;
}else {
return 0;
}
}
});
System.out.println(userList);
在Java8以后可以使用Lamda表达式来进行函数式地编程:
userList.sort((a, b) -> Integer.compare(a.getAge(), b.getAge()));
=====================================================================================
Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不相关的东西。
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。
下面是使用流的过程:
下面是一个使用流的实例,用于List的迭代:
List stringList = new ArrayList();
stringList.add(“one”);
stringList.add(“two”);
stringList.add(“three”);
stringList.add(“one”);
Stream stream = stringList.stream();
stream.forEach( element -> { System.out.println(element); });
=========================================================================
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。
传统的session认证一般是这样的流程:
-
1、用户向服务器发送用户名和密码。
-
2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
-
3、服务器向用户返回一个 session_id,写入用户的 Cookie。
-
4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
-
5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
一种解决方案是 session共享,将session持久化或者存入缓存。各种服务收到请求后,都向持久层或缓存请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层或者缓存万一挂了,就会认证失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
JWT认证流程:
-
1、 用户使用账号和密码发出post请求;
-
2、 服务器使用私钥创建一个jwt;
-
3、 服务器返回这个jwt给浏览器;
-
4、 浏览器将该jwt串在请求头中像服务器发送请求;
-
5、 服务器验证该jwt;
-
6、 返回响应的资源给浏览器。
===========================================================================
事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作, 这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行 。
事务是一个不可分割的工作逻辑单元事务必须具备以下四个属性,简称 ACID 属性:
-
原子性(Atomicity) :事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执行。
-
一致性(Consistency): 当事务完成时,数据必须处于一致状态。
-
隔离性(Isolation) :对数据进行修改的所有并发事务是彼此隔离的, 这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务。
-
永久性(Durability) : 事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性
=============================================================================
通过在方法加注解 @Transactional 来实现声明式的事务。
Spring 事务管理分为编码式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。
写在最后
为了这次面试,也收集了很多的面试题!
以下是部分面试题截图
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
解 @Transactional 来实现声明式的事务。
Spring 事务管理分为编码式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。
写在最后
为了这次面试,也收集了很多的面试题!
以下是部分面试题截图
[外链图片转存中…(img-i1KVPWAf-1713672101981)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-MbkXR2ac-1713672101982)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!