// Bug.addStatic();//静态方法同步
}
}
测试代码
public static void main(String[] args) {
BugRunnable bugRunnable = new BugRunnable();
for (int i = 0; i < 6; i++) {
new Thread(bugRunnable).start();
}
}
同步代码块
//同步代码块
public synchronized void addBlock() {
synchronized (bugNumber) {
this.bugNumber = ++bugNumber;
System.out.println(“blockSynchronized—>” + getBugNumber());
}
}
测试结果
blockSynchronized—>1
blockSynchronized—>2
blockSynchronized—>3
blockSynchronized—>4
blockSynchronized—>5
blockSynchronized—>6
普通方法同步
//普通同步方法
public synchronized void addNormal() {
bugNumber++;
System.out.println(“normalSynchronized—>” + getBugNumber());
}
测试结果
normalSynchronized—>1
normalSynchronized—>2
normalSynchronized—>3
normalSynchronized—>4
normalSynchronized—>5
normalSynchronized—>6
静态方法同步
//静态同步方法
public static synchronized void addStatic() {
bugNumber++;
System.out.println(“staticSynchronized—>” + getBugNumber());
}
测试结果
staticSynchronized—>1
staticSynchronized—>2
staticSynchronized—>3
staticSynchronized—>4
staticSynchronized—>5
staticSynchronized—>6
对比分析
-
类的每个实例都有自己的对象锁。当一个线程访问实例对象中的synchronized同步代码块或同步方法时,该线程便获取了该实例的对象级别锁,其他线程这时如果要访问同一个实例(因为对象可以有多个实例)同步代码块或同步方法,必须等待当前线程释放掉对象锁才可以,如果是访问类的另外一个实例,则不需要。
-
如果一个对象有多个同步方法或者代码块,没有获取到对象锁的线程将会被阻塞在所有同步方法之外,但是可以访问非同步方法
-
对于静态方法,实际上可以把它转化成同步代码块,就拿上面的静态方法,实际上相当于:
//静态同步方法
public static synchronized void addStatic() {
bugNumber++;
System.out.println(“staticSynchronized—>” + getBugNumber());
}
//用同步代码块
public static void changeStatic() {
synchronized (Bug.class) {
++bugNumber;
System.out.println(“blockSynchronized—>” + getBugNumber());
}
}
下面具体来总结一下三者的区别
-
同步代码块:同步代码块的范围较小,只是锁定了某个对象,所以性能较高
-
普通同步方法:给整个方法上锁,性能较低
-
静态同步方法:相当于整个类的同步代码块,性能较低
ReentrantLock
除了synchronized这个关键字外,我们还能通过concurrent包下的Lock接口来实现这种效果,ReentrantLock是lock的一个实现类,可以在任何你想要的地方进行加锁,比synchronized关键字更加灵活,下面看一下使用方式
使用方式
//ReentrantLock同步
public void addReentrantLock() {
mReentrantLock.lock();//上锁
bugNumber++;
System.out.println(“normalSynchronized—>” + getBugNumber());
mReentrantLock.unlock();//解锁
}
运行测试
ReentrantLock—>1
ReentrantLock—>2
ReentrantLock—>3
ReentrantLock—>4
ReentrantLock—>5
ReentrantLock—>6
我们发现也是可以达到同步的目的,看一下ReentrantLock的继承关系
![image](https://img-
blog.csdnimg.cn/img_convert/384f4451c4154d9b6625a0f42a40248f.webp?x-oss-
process=image/format,png)
ReentrantLock实现了lock接口,而lock接口只是定义了一些方法,所以相当于说ReentrantLock自己实现了一套加锁机制,下面简单分析一下ReentrantLock的同步机制,在分析前,需要知道几个概念:
-
CLH:AbstractQueuedSynchronizer中“等待锁”的线程队列。在线程并发的过程中,没有获得锁的线程都会进入一个队列,CLH就是管理这些等待锁的队列。
-
CAS:比较并交换函数,它是原子操作函数,也就是说所有通过CAS操作的数据都是以原子方式进行的。
成员变量
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;//同步器
成员变量除了序列化ID之外,只有一个Sync,那就看一看具体是什么
![image](https://img-
blog.csdnimg.cn/img_convert/57094d0d23e4975230a64909eea8de70.webp?x-oss-
process=image/format,png)
Sync有两个实现类,一个是FairSync,一个是NonfairSync,从名字可以大致推断出一个是公平锁,一个是非公平锁,
FairSync(公平锁)
lock方法:
final void lock() {
acquire(1);
}
ReentrantLock是独占锁,1表示的是锁的状态state。对于独占锁而言,如果所处于可获取状态,其状态为0,当锁初次被线程获取时状态变成1,acquire最终调用的是tryAcquire方法
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 当c==0表示锁没有被任何线程占用
(hasQueuedPredecessors),
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//锁已经被线程占用
int nextc = c + acquires;
if (nextc < 0)
throw new Error(“Maximum lock count exceeded”);
setState(nextc);
return true;
}
return false;
}
tryAcquire主要是去尝试获取锁,获取成功则设置锁状态并返回true,否则返回false
NonfairSync(非公平锁)
非公平锁NonfairSync的lock()与公平锁的lock()在获取锁的流程上是一直的,但是由于它是非公平的,所以获取锁机制还是有点不同。通过前面我们了解到公平锁在获取锁时采用的是公平策略(CLH队列),而非公平锁则采用非公平策略它无视等待队列,直接尝试获取。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
lock()通过compareAndSetState尝试设置锁的状态,若成功直接将锁的拥有者设置为当前线程(简单粗暴),否则调用acquire()尝试获取锁,对比一下,公平锁跟非公平锁的区别在于tryAcquire中
//NonfairSync
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//FairSync
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
公平锁中要通过hasQueuedPredecessors()来判断该线程是否位于CLH队列头部,是则获取锁;而非公平锁则不管你在哪个位置都直接获取锁。
unlock
public void unlock() {
sync.release(1);//释放锁
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
对比分析
等待可中断
-
synchronized:线程A跟线程B同时竞争同一把锁,如果线程A获得锁之后不释放,那么线程B会一直等待下去,并不会释放。
-
ReentrantLock:可以在线程等待了很长时间之后进行中断,不需要一直等待。
锁的公平性
公平锁:是指多个线程在等待同一个锁时,必须按照申请的时间顺序来依次获得锁;非公平锁:在锁被释放时,任何一个等待锁的线程都有机会获得锁;
-
synchronized:是非公平锁
-
ReentrantLock:可以是非公平锁也可以是公平锁
绑定条件
-
synchronized中默认隐含条件。
-
ReentrantLock可以绑定多个条件
可见性
volatile
内存语义
由于多个线程方法同一个变量,导致了线程安全问题,主要原因是因为线程的工作副本的变量跟主内存的不一致,如果能够解决这个问题就可以保证线程同步,而Java提供了volatile关键字,可以帮助我们保证内存可见性,当我们声明了一个volatile关键字,实际上有两层含义;
-
禁止进行指令重排序。
-
一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。
原理
在使用volatile关键字的时候,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
使用场景
这里需要强调一点,volatile关键字并不一定能保证线程同步,如果非要采用volatile关键字来保证线程同步,则需要满足以下条件:
-
对变量的写操作不依赖于当前值
-
该变量没有包含在具有其他变量的不变式中
其实看了一些书跟博客,都是这么写的,按照我的理解实际上就是只有当volatile修饰的对象是原子性操作,才能够保证线程同步,为什么呢。
测试代码:
class Volatile {
volatile static int count = 0;
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Volatile.add();
}
}).start();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“count—>” + ++count);
}
private static void add() {
count++;
}
}
运行结果
count—>1001
理论上是1000才对,但是输出的值是1001,为什么呢,这个其实在之前的JMM中已经分析过了,下面再贴一张图
![image](https://img-
blog.csdnimg.cn/img_convert/5fa33b893cda6249cf952bc5ee64a47b.webp?x-oss-
process=image/format,png)
跟之前一样,我们每次从主内存中获取到的count确实是最新的,但是由于对count的操作不是原子性操作,假如现在有两个线程,线程1跟线程2,如果线程1读取到了count值是5,然后read—>load进内存了,然后现在被线程2抢占了CPU,那么线程2就开始read—>load,并且完成了工作副本的赋值操作,并且将count
的值回写到主内存中,由于线程1已经进行了load操作,所以不会再去主内存中读取,会接着进行自己的操作,这样的话就出现了线程不安全,所以volatile必须是原子性操作才能保证线程安全。
基于以上考虑,volatile主要用来做一些标记位的处理:
volatile boolean flag = false;
//线程1
while(!flag){
doSomething();
}
//线程2
public void setFlag() {
flag = true;
}
当有多个线程进行访问的时候,只要有一个线程改变了flag的状态,那么这个状态会被刷新到主内存,就会对所有线程可见,那么就可以保证线程安全。
automatic
automatic是JDK1.5之后Java新增的concurrent包中的一个类,虽然volatile可以保证内存可见性,大部分操作都不是原子性操作,那么volatile的使用场景就比较单一,然后Java提供了automatic这个包,可以帮助我们来保证一些操作是原子性的。
使用方式
替换之前的volatile代码
public static AtomicInteger atomicInteger = new AtomicInteger(0);
private static void add() {
atomicInteger.getAndIncrement();
}
测试一下:
AtomicInteger: 1000
原理解析
AtomicInteger既保证了volatile保证不了的原子性,同时也实现了可见性,那么它是如何做到的呢?
成员变量
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;
运算方式
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
int compare_and_swap(int reg, int oldval, int newval) {
ATOMIC();
int old_reg_val = reg;
if (old_reg_val == oldval)
reg = newval;
END_ATOMIC();
return old_reg_val;
}
分析之前需要知道两个概念:
-
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
-
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
compare_and_swap这个才是核心方法,也就是上面提到的CAS,因为CAS是基于乐观锁的,也就是说当写入的时候,如果寄存器旧值已经不等于现值,说明有其他CPU在修改,那就继续尝试。所以这就保证了操作的原子性。
变量私有化
这种方式实际上指的就是ThreadLocal,翻译过来是线程本地变量,ThreadLocal会为每个使用该变量的线程提供独立的变量副本,但是这个副本并不是从主内存中进行读取的,而是自己创建的,每个副本相互之间独立,互不影响。相对于syncronized的以时间换空间,ThreadLocal刚好相反,可以减少线程并发的复杂度。
简单使用
class ThreadLocalDemo {
public static ThreadLocal local = new ThreadLocal<>();//声明静态的threadlocal变量
public static void main(String[] args) {
local.set(“Android”);
for (int i = 0; i < 5; i++) {
SetThread localThread = new SetThread();//创建5个线程
new Thread(localThread).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(local.get());
}
static class SetThread implements Runnable {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
![img](https://img-
blog.csdnimg.cn/img_convert/3fc851ad480c016fd52c7b267f5382e0.png)
![img](https://img-
blog.csdnimg.cn/img_convert/7479e4d04951fcef5b2ba7215ab18515.png)
![img](https://img-
blog.csdnimg.cn/img_convert/0e09bf72c66b9568b0594a6a5bc7ac71.png)
![img](https://img-
blog.csdnimg.cn/img_convert/8e91492b57bd58eb519bfd167459f4c5.png)
![](https://img-
blog.csdnimg.cn/img_convert/edb7b51cdbbf5b1a47961dacc59c3ffb.png)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://img-
blog.csdnimg.cn/img_convert/a7dd793b022b13d1eb49329f9f0fdd8b.jpeg)
最后
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
上面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题
,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节
,由于篇幅有限,上面只是以图片的形式给大家展示一部分。
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
![](https://img-
blog.csdnimg.cn/img_convert/398d03df75a65c4817d808f33a74f5a0.webp?x-oss-
process=image/format,png)
【Android高级架构视频学习资源】
**Android部分精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
2024392853)]
[外链图片转存中…(img-JyPVZCdX-1712024392853)]
[外链图片转存中…(img-u3icbk1D-1712024392853)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://img-
blog.csdnimg.cn/img_convert/a7dd793b022b13d1eb49329f9f0fdd8b.jpeg)
最后
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
上面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题
,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节
,由于篇幅有限,上面只是以图片的形式给大家展示一部分。
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
[外链图片转存中…(img-car4Vw4I-1712024392853)]
【Android高级架构视频学习资源】
**Android部分精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
接下来我将给各位同学划分一张学习计划表!
学习计划
那么问题又来了,作为萌新小白,我应该先学什么,再学什么?
既然你都问的这么直白了,我就告诉你,零基础应该从什么开始学起:
阶段一:初级网络安全工程师
接下来我将给大家安排一个为期1个月的网络安全初级计划,当你学完后,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web渗透、安全服务、安全分析等岗位;其中,如果你等保模块学的好,还可以从事等保工程师。
综合薪资区间6k~15k
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)
2、渗透测试基础(1周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等
3、操作系统基础(1周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)
4、计算机网络基础(1周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现
5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固
6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)
那么,到此为止,已经耗时1个月左右。你已经成功成为了一名“脚本小子”。那么你还想接着往下探索吗?
阶段二:中级or高级网络安全工程师(看自己能力)
综合薪资区间15k~30k
7、脚本编程学习(4周)
在网络安全领域。是否具备编程能力是“脚本小子”和真正网络安全工程师的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力。
零基础入门的同学,我建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习
搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP,IDE强烈推荐Sublime;
Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,没必要看完
用Python编写漏洞的exp,然后写一个简单的网络爬虫
PHP基本语法学习并书写一个简单的博客系统
熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选)
了解Bootstrap的布局或者CSS。
阶段三:顶级网络安全工程师
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!
学习资料分享
当然,只给予计划不给予学习资料的行为无异于耍流氓,这里给大家整理了一份【282G】的网络安全工程师从入门到精通的学习资料包,可点击下方二维码链接领取哦。