最新【JavaEE多线程】线程安全、锁机制及线程间通信,2024年最新程序员如何应对中年危机

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

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

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

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

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

需要这份系统化资料的朋友,可以点击这里获取

    Thread t1=new Thread(()->{
        for (int i = 0; i < 50000; i++) {
            counter.increace();//这里t1的counter和下面t2的counter进行锁竞争/锁冲突
        }
    });
    Thread t2=new Thread(()->{
        for (int i = 0; i < 50000; i++) {
            counter.increace();//这里t2的counter和上面t1的counter进行锁竞争/锁冲突
        }
    });

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    System.out.println(counter.count);
}

}


如果是**两个线程针对同一个对象进行加锁,就会出现锁竞争/锁冲突**(**一个线程能加锁成功,另一个线程阻塞等待**)


如果是两个线程针对不同对象进行加锁,就不会出现锁竞争/锁冲突,也就不存在阻塞等待的操作了


因此具体针对哪个对象加锁不重要,**重要的是两个线程,是不是针对同一个对象加锁**


* **思考:如果接下来的代码里,一个线程加锁了,一个线程没加锁,此时是否还会存在线程安全问题**
* **答:单方面加锁等于没加锁,必须得多个线程都对同一个对象加锁,才有意义**


synchronized的底层是使用操作系统的mutex lock实现的.


**synchronized有且只有一条规则:**


**当两个线程针对同一个对象加锁的时候,就会出现锁竞争/锁冲突。一个线程能先拿到锁,另一个线程就会阻塞等待(BLOCKED)。直到第一个线程释放了锁之后,第二个线程才可能获取到锁,才能继续往下执行。**


##### 刷新内存


synchronized 的工作过程:


1. 获得互斥锁
2. 从主内存拷贝变量的最新副本到工作的内存
3. 执行代码
4. 将更改后的共享变量的值刷新到主内存
5. 释放互斥锁


##### 可重入


synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题



> 
> **理解** **"把自己锁死**"
> 
> 
> 一个线程没有释放锁, 然后又尝试再次加锁.
> 
> 
> 
> ```
> // 第一次加锁, 加锁成功
> lock();
> // 第二次加锁, 锁已经被占用, 阻塞等待. 
> lock();
> 
> ```
> 
> 按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第一次的锁被释放, 才能获取到第二个锁. 但是释放第一个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行解锁操作. 这时候就会 **死锁**.
> 
> 
> 这样的锁称为 **不可重入锁**.
> 
> 
> 


Java 中的 synchronized 是 **可重入锁**, 因此没有上面的问题


**代码示例**


在下面的代码中,


* increase 和 increase2 两个方法都加了 synchronized, 此处的 synchronized 都是针对 this 当前对象加锁的.
* 在调用 increase2 的时候, 先加了一次锁, 执行到 increase 的时候, 又加了一次锁. (上个锁还没释放, 相当于连续加两次锁)


这个代码是完全没问题的. 因为 synchronized 是可重入锁.



static class Counter {
public int count = 0;
synchronized void increase() {
count++;
}
synchronized void increase2() {
increase();
}
}


在可重入锁的内部, 包含了 “线程持有者” 和 “计数器” 两个信息.


* 如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增.
* 解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)


#### synchronized使用范例


synchronized 本质上要修改指定对象的 “对象头”. 从使用角度来看, synchronized 也势必要搭配一个具体的对象来使用.


1. **直接修饰普通方法**:锁的 SynchronizedDemo 对象



public class SynchronizedDemo {
public synchronized void methond() {
}
}


2. **修饰静态方法**: 锁的 SynchronizedDemo 类的对象



public class SynchronizedDemo {
public synchronized static void method() {
}
}


3. **修饰代码块**: 明确指定锁哪个对象.


锁当前对象



public class SynchronizedDemo {
public void method() {
synchronized (this) {//Test test=new Test()的test也行

   }
}

}


锁类对象



public class SynchronizedDemo {
public void method() {
synchronized (SynchronizedDemo.class) {
}
}
}


### volatile


#### volatile能保证内存可见性


volatile 修饰的变量, 能够保证 “内存可见性”.


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2a3c2f1e722d441f9bd3b654d4e3874b.png#pic_center)


代码在写入 volatile 修饰的变量的时候,


* 改变线程工作内存中volatile变量副本的值
* 将改变后的副本的值从工作内存刷新到主内存


代码在读取 volatile 修饰的变量的时候,


* 从主内存中读取volatile变量的最新值到线程的工作内存中
* 从工作内存中读取volatile变量的副本


**代码示例**


在这个代码中


* 创建两个线程 t1 和 t2
* t1 中包含一个循环, 这个循环以 flag == 0 为循环条件.
* t2 中从键盘读入一个整数, 并把这个整数赋值给 flag.
* 预期当用户输入非 0 的值的时候, t1 线程结束.



static class Counter {
public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (counter.flag == 0) {
// do nothing
}
System.out.println(“循环结束!”);
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println(“输入一个整数:”);
counter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}
// 执行效果
// 当用户输入非0值时, t1 线程循环不会结束. (这显然是一个 bug)
// 注意:这里直接在控制台是看不出的



> 
> t1 读的是自己工作内存中的内容.
> 
> 
> 当 t2 对 flag 变量进行修改, 此时 t1 感知不到 flag 的变化.
> 
> 
> 


如果给 flag 加上 volatile



static class Counter {
public volatile int flag = 0;
}
// 执行效果
// 当用户输入非0值时, t1 线程循环能够立即结束.
// 注意:这里直接在控制台同样是看不出的


#### volatile不保证原子性


volatile 和 synchronized 有着本质的区别. synchronized 能够保证原子性, volatile 保证的是内存可见性.


#### synchronized 也能保证内存可见性


synchronized 既能保证原子性, 也能保证内存可见性.


**内存可见性问题:**


1. 编译器优化
2. 内存模型
3. 多线程


* volatile保证的是内存可见性,不是原子性


**内存可见性**和**加锁**描述了线程安全问题的典型情况和处理方式


### wait 和 notify


由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.


但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.


完成这个协调工作, 主要涉及到三个方法 :


* wait() / wait(long timeout): 让当前线程进入等待状态.
* notify() / notifyAll(): 唤醒在当前对象上等待的线程.



> 
> **注意**: wait, notify, notifyAll 都是 Object 类的方法.
> 
> 
> wait(等待)和notify(通知)就是一个用来协调线程顺序的重要工具
> 
> 
> 这两个方法都是Object提供的方法,随便找个对象都可以调用
> 
> 
> 


当wait引起线程阻塞之后,可以使用interrupt方法把线程唤醒,打断当前线程的阻塞状态


#### wait()方法


**wait在执行的时候,会做三件事:**


1. 解锁。object.wait,就会尝试针对object对象解锁
2. 阻塞等待
3. 当被其他线程唤醒之后,就会尝试重新加锁,加锁成功,wait执行完毕,继续往下执行其他逻辑。


wait要解锁前提是先能加上锁


* 核心解决思路:先加锁,在synchronized里头再wait,这样子的wait就会一直阻塞到其他线程进行notify了


**注意事项**:


1. 要想让notify能够顺利唤醒wait,就需要确保wait和notify都是使用同一个对象调用的。
2. wait和notify都需要放到synchronized之内的。虽然notify不涉及“解锁操作”,但是Java也强制要求notify要放到synchronized中。(系统的原生api中就没有这个要求)
3. 如果进行notify的时候,另一个线程并没有处于wait状态,此时,notify相当于“空打一炮”,不会有任何副作用


**代码示例**: 观察wait()方法使用



public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
System.out.println(“等待中”);
object.wait();
System.out.println(“等待结束”);
}
}


这样在执行到object.wait()之后就一直等待下去,那么程序肯定不能一直这么等待下去了。这个时候就需要使用到了另外一个方法唤醒的方法notify()


线程可能有多个,比如可以有n个线程进行wait一个线程负责notify,notify操作只会唤醒一个线程。具体是唤醒了哪个线程?是随机的!


**wait和sleep的区别:**


* sleep有个明确的时间,到达时间自然就会被唤醒,也能提前唤醒,使用interrupt
* wait默认是个死等,一直等到其他线程notify,wait也能被interrupt提前唤醒


#### notify()方法


notify 方法是唤醒等待的线程.


* 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
* 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
* 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。


**代码示例**: 使用notify()方法唤醒线程


* 创建 WaitTask 类, 对应一个线程, run 内部循环调用 wait.
* 创建 NotifyTask 类, 对应另一个线程, 在 run 内部调用一次 notify
* 注意:WaitTask 和 NotifyTask 内部持有同一个 Object locker. WaitTask 和 NotifyTask 要想配合就需要搭配同一个 Object.




### 一、网安学习成长路线图


网安所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/aa7be04dc8684d7ea43acc0151aebbf1.png)


### 二、网安视频合集


观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f0aeee2eec7a48f4ad7d083932cb095d.png)


### 三、精品网安学习书籍


当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/078ea1d4cda342f496f9276a4cda5fcf.png)


### 四、网络安全源码合集+工具包


光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/e54c0bac8f3049928b488dc1e5080fc5.png)


### 五、网络安全面试题


最后就是大家最关心的网络安全面试题板块  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/15c1192cad414044b4dd41f3df44433d.png)![在这里插入图片描述](https://img-blog.csdnimg.cn/b07abbfab1fd4edc800d7db3eabb956e.png)  



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

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

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

928b488dc1e5080fc5.png)


### 五、网络安全面试题


最后就是大家最关心的网络安全面试题板块  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/15c1192cad414044b4dd41f3df44433d.png)![在这里插入图片描述](https://img-blog.csdnimg.cn/b07abbfab1fd4edc800d7db3eabb956e.png)  



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

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值