java学习笔记:并发编程实战,软件售后技术支持面试题

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

BigInteger i = extractFromRequest(req);

BigInteger[] factors = factor(i);

encodeIntoResponse(resp, factors);

}

}

这个StatelessFactorizer是无状态的:它既不包含任何域,也不包含任何对其他类中域的引用。方法中的局部变量只能由正在执行的线程访问。如果同时有多个线程在访问StatelessFactorizer,那么这些线程之间将不会互相影响,因为线程之间并没有共享状态,就好像在访问不同的实例。

由于线程访问无状态对象的行为并不会影响其他线程中操作的正确性,因此无状态对象是线程安全的,且无状态对象一定是线程安全的。

二、原子性

=====

什么是原子性呢?其实原子性就是一个不可再分割的性质,不能再分成更细的粒度。

如果我们在刚刚的示例中增加一个状态(既一个计数器),用来统计已处理请求数量,每处理一个请求就将这个值加1,程序如下所示:

public class StatelessFactorizer implements Servlet{

private long count = 0;

public long getCount(){return count;}

public void service(ServletRequest req, ServletResponse resp){

BigInteger i = extractFromRequest(req);

BigInteger[] factors = factor(i);

++count;

encodeIntoResponse(resp, factors);

}

}

在上面的程序示例中,咋一看没问题,++count看起来像是一个操作,但是这个自增操作并非原子性的。因为实际上,它包含了三个操作:“读取-修改-写入”的操作序列。每个操作都依赖于前面之前的状态。如果此时有两个线程A、B,如果A线程已经进行到了修改操作,此时如果B线程进行了读取,那么最终A、B线程写入的值是一样的,这样就与预期结果偏差了1.

虽然在这里看起来,结果偏离了一些可以接受,但是如果这个计数器的值被用来生成数值序列或唯一的对象标识符,那么在多次调用中返回相同的值将导致严重的数据完整性问题。

在并发编程中,像这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,这种情况叫做“竞态条件(Race Condition)”。

竞态条件

当某个计算的正确性取决于多个线程的交替执行时序的时候,那么就会发生竞态条件。常见的竞态条件类型是“先检查后执行”操作,既通过一个可能失效的观测结果来决定下一步的动作。

举个栗子:你和朋友约好一起去网吧开黑,你当了网吧的时候,发现你朋友不在,此时你可能选择呆在网吧里等他,也可能去他家找他,如果你去找他,那么当你出了网吧以后,你在网吧的观测结果(朋友不在)就可能失效了,因为他可能在你去他家找他的路上已经到了网吧,而你却去找他了。

这个栗子中,正确的结果是(你们在网吧会面),但是这个结果取决于事件发生的时序(既谁先到网吧并且等待对方的时长)。这种观察结果的失效就是大多数竞态条件的本质——基于一种可能失效的观测结果来做出判断或者执行某个计算。

再举个栗子,假设有两个线程A、B,A、B线程都用来判断某个文件夹是否存在,不存在就创建它,假如当A线程发现文件夹不存在时,正打算创建文件夹,但是此时B线程已经完成了文件夹的创建,那么此时A线程观测的结果就已经失效了,但是A线程依旧根据这个已失效的观测结果在进行下一步动作,这就可能会导致各种问题。

使用“先检查后执行”的一种常见的情况就是延迟初始化。就比如在单例模式中有一种写法如下:

public class LazyInitRace {

private static LazyInitRace instance = null;

public LazyInitRace getInstance(){

if(instance == null){

instance = new LazyInitRace();

}

return instance;

}

}

这就是典型的延迟初始化,在单线程中这样写没毛病,但是在多线程环境中,如果有A、B线程同时执行getInstance()方法,那么结果可能符合预期,也可能会得到两个不一样的对象。因为在A线程发现instace为null时,B线程可能也同时发现instace为null。

与大多数并发错误一样,竞态条件并不总是会产生错误,还需要某种不恰当的执行时序,但是如果发生问题,那么可能导致很严重的问题。

在上面的示例中都包含了一组需要以原子方式执行(或者说不可分割)的操作。要避免竞态条件问题,就必须在某个线程修改变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或之后读取和修改状态,而不是在修改过程中。

在上面统计已处理请求数量的示例中,我们可以使用AtomicLong对象来替换long,因为AtmoicLong类是线程安全类,所以可以保证示例也是示例安全的,但是在添加一个状态变量时,是否还可以通过使用线程安全的对象来管理而类的状态以维护其线程安全性呢?如下所示:

public class UnsafeCachingFactorizer implements Servlet {

private final AtomicReference lastNumber = new AtomicReference<>();

private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<>();

public void service(ServletRequest req, ServletResponse resp) {

BigInteger i = extractFromRequest(req);

if (i.equals(lastNumber.get())) {

encodeIntoResponse(resp, lastFactors.get());

} else {

BigInteger[] factors = factor(i);

lastNumber.set(i);

lastFactors.set(factors);

encodeIntoResponse(resp, factors);

}

}

}

在上述例子中,虽然两个变量都是线程安全的,但是在service方法中依然存在竞态条件,因为在上述例子中,类的不变性条件已经被破坏了,只有确保了这个不变性条件不被破坏,才是正确的。当不变性条件中涉及到了多个变量时,各个变量之间并不是彼此独立的,而是某个变量的值会对其他变量的值产生约束。因此,当更新某一个变量时,需要在同一个原子操作中对其他变量同时进行更新。

在上述例子中,虽然set方法是原子操作,但是在set方法无法同时更新lastNumber和lastFactors。如果当一个线程执行了lastNumber.set()方法还没执行下一个set方法时,如果此时有一个线程访问service方法,那么得到的结果就与我们所预期的不一致了。

所以,要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

三、加锁机制

======

3.1内置锁

在Java中提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包含两部分:一个是作为锁的对象引用,一个作为由这个锁保护的代码块。以关键字synchronized来修饰的方法是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象(this).静态的synchronized方法以Class对象为作为锁。

每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或是监视器锁(Monitor Lock)。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁。

Java的内置锁相当于一种互斥锁,最多只有一个线程能持有这种锁。当线程A尝试获取线程B持有的锁时,线程A必须等待或阻塞,知道线程B释放了该锁。如果线程B不释放锁,则线程A也将永远等下去。任何一个执行同步代码块的线程,都不可能看到有其他线程正在执行由同一个锁保护的同步代码块。

总结

阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了

image

1、JAVA面试核心知识整理(PDF):包含JVMJAVA集合JAVA多线程并发,JAVA基础,Spring原理微服务,Netty与RPC,网络,日志,ZookeeperKafkaRabbitMQ,Hbase,MongoDB,Cassandra,设计模式负载均衡数据库一致性哈希JAVA算法数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。

image

2、Redis学习笔记及学习思维脑图

image

3、数据面试必备20题+数据库性能优化的21个最佳实践

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
化的21个最佳实践

[外链图片转存中…(img-XrrDcvTM-1713412567874)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-l2NABYsz-1713412567874)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值