java是面向对象的,把现实生活当中事物的特性抽取出来,形成一个类,类中有很多这个事物的属性特点,但是不是所有属性都可以被外界获取到,比如女神的年龄age,不会提供getAge方法去获取
继承允许一个类(子类)继承现有类(父类或者基类)的属性和方法,以提高代码的复用性。子类还可以重写或者扩展从父类继承来的属性和方法,从而实现多态。
===============================
多态的前置条件有三个:子类继承父类,子类重写父类的方法,父类引用指向子类的对象
多态其实是一种能力——同一个行为具有不同的表现形式;animal,cat,dog
方法名相同,(参数个数不同)(参数类型不同)
子类和父类有一样的方法(方法名相同)(参数相同)(返回类型)(方法体不同)
口述:浅拷贝的实现方式为:实现 Cloneable 接口并重写 clone()
方法。
如果被拷贝的类当中它的成员变量中有引用类型——就是对象嘛,那我们拷贝过来的是引用,而不是一个副本
class Person implements Cloneable {
String name;
int age;
Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Address {
String city;
public Address(String city) {
this.city = city;
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("河南省洛阳市");
Person person1 = new Person("沉默王二", 18, address);
Person person2 = (Person) person1.clone();
System.out.println(person1.address == person2.address); // true
}
}
深拷贝两种方式
手动拷贝和序列化反序列化拷贝
class Person {
String name;
int age;
Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public Person(Person person) {
this.name = person.name;
this.age = person.age;
this.address = new Address(person.address.city);
}
}
class Address {
String city;
public Address(String city) {
this.city = city;
}
}
public class Main {
public static void main(String[] args) {
Address address = new Address("河南省洛阳市");
Person person1 = new Person("沉默王二", 18, address);
Person person2 = new Person(person1);
System.out.println(person1.address == person2.address); // false
}
}
import java.io.*;
class Person implements Serializable {
String name;
int age;
Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public Person deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Person) ois.readObject();
}
}
class Address implements Serializable {
String city;
public Address(String city) {
this.city = city;
}
}
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Address address = new Address("河南省洛阳市");
Person person1 = new Person("沉默王二", 18, address);
Person person2 = person1.deepClone();
System.out.println(person1.address == person2.address); // false
}
}
一个类只能继承一个抽象类;但一个类可以实现多个接口(不支持多继承)(接口优先:Runnable接口的方式)
接口更多的是一种行为标准,抽象类更多的是为多个相关类提供一个公共的基础框架,
接口不能定义构造方法,抽象类可以有构造方法
抽象类使用 abstract 关键字定义,不能被实例化,里面的抽象方法没有方法体,必须由子类实现
以字节为单位读写数据,主要用于处理二进制数据,如音频、图像文件等。
我的项目中去返回音乐文件就用到了字节流相关的东西,比如说:File.readAllBytes()
考虑有一本书。如何快速找到一个章节所在的位置,就需要一个目录,根据目录,就可以快速定位到一个章节。
因为在进行查询的时候,经常需要遍历表,但是数据库是把数据存储在硬盘上,这里的Io读写操作是非常耗费时间的。因此就可以给数据库引入索引,来提高查询的速度。
比如说我们的parimary key 和 foreign key 和 unique 都会自动生成索引,索引自身是一定的数据结构,也要占据存储空间,也是牺牲了空间换取了时间,
底层的数据结构一般是B+树,先介绍B树
B树是一个有序的N叉搜索树,每一个节点上有N个值,N个值划分出来N+1个区间
同样高度的B树和二叉搜索树,B树能表示的元素个数更多
虽然搜索的时候B树的比较次数更多,但是B树的硬盘IO读取次数更少,因为比较是在内存当中嘛,遍历是在硬盘当中嘛,
B+树
B+树也是N叉搜索树,划分出来N个区间,根节点中出现的值,在子树中会重复出现,在这个树最下面一层的叶子节点中会涵盖所有的数据,第一点:查询的时间是稳定的;第二点:无需回溯;第三点:存储数据很方便,给叶子节点下面挂一个类似HashMap键值对的结构,有点像Session的结构,哈希表中的哈希表
引入转账的情景:张三在银行账户中存有1000元,李四存有500元,这时张三要给李四支付500元,执行sql语句,如果执行完张三的扣款操作之后,数据库挂了,那不仅张三被扣了钱,李四还没有收到钱,问题就麻烦了
引出回滚和恢复机制(undo log,redo log):数据库把操作写成日志的形式保存到文件中,当数据库挂了,重启之后,会检查日志中是否有只执行了一半或者没有执行完的操作,如果有,就会把之前的操作进行回滚
并发操作引出的bug:
⑤并发事务bug:脏读,可重复读,幻读
Bug(1):“脏读”问题 ①场景引入 服务器A:对数据库发出事务请求,修改了某个数据(写),但是还没有“提交”(提交的意思就是,告诉数据库,我的操作OK了,结束了) 服务器B:同时对数据库进行读取,读取了这个数据,但是这个数据并不一定是准确的,因为A后续还可能对数据进行修改,所以B的这一次读取操作就是“脏读” 通俗解释:考试中张三在写卷子,我过去瞄了一眼他写的答案,但是张三后面又修改成了正确答案,导致我抄的答案其实是错误的 ②解决思路(给“写”上锁) 给操作“上锁”,在A对数据库操作的时候,(上锁),其它服务器不能访问,等到A的操作完成之后(解锁),后面的服务器才可以进行操作(看) 注意:这里的上锁是针对(写操作)服务器A 通俗解释:就是去上厕所,一个坑只能一个人上,坑里的人开门出来了,你才能进去 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2301_80133875/article/details/142180255
Bug(2):不可重复读 ①场景引入(沿用Bug1的场景) 服务器A在访问(写)数据库时候上锁了,服务器B在A结束操作之后开始第一次读取,此时进来一个服务器C(写)访问服务器,修改了数据,服务器B第二次读取数据发现:嘿怎么两次读取的数据不一致??? 注:上锁是针对,服务器在数据库(写)修改数据的情况,没说你在读的时候,我不能修改呀!! ②解决思路(给“读”上锁) 在服务器“读”的时候也进行上锁。 不难发现Bug(2)和Bug(1)很像,就是(1)的一个延伸。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2301_80133875/article/details/142180255
Bug(3):幻读 ①场景引入(沿用Bug2的场景) 服务器A上锁修改数据库数据,解锁后,服务器B开始第一次上锁读取,此时服务器C不修改数据了,C新增了一个数据,B第二次读取发现“结果集”发生了变化,王德发?? 解释“结果集”:就是类似表的行数 ②解决思路() 把并行事务串口化,不再进行任何并行开发,使用串口开发,一项事务执行完毕后,再继续下一项(实际开发中并行,串行视情况而定) ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2301_80133875/article/details/142180255
⑥隔离级别:读未提交,读已提交,可重复读,串行化
对于并发程度不是很高的场景来说,还是要优先去考虑效率
1:read uncommitted(读未提交) 并行程度(高),隔离级别(低),效率(高),数据的准确性(低),可能会触发:“脏读”,“不可重复读”,“幻读” 2:read committed(读已提交) 并行程度(中),隔离级别(中),效率(中),数据的准确性(中),可能会触发:“不可重复读”,“幻读” 3:repeatable read (可重复读) 并行程度(低),隔离级别(高),效率(低),数据准确性(高),可能会触发:“幻读” 4:serializable(串行化) 并行程度(无了),隔离级别(最高),效率(最低),数据准确性(最准)
表联合核心操作就是笛卡尔积 第一种写法就是在from后面加上两个表名select * from student , score; 第二种写法:select * from student join score;
数据库里的每一个列都是最基本的独立的个体,不能再拆分(列不能拆分)
要求表中的数据要和主键有紧密关系(数据要和主键有紧密关系)
要求表中的非主键列之间不能有间接的依赖关系(非主键列之间不能有间接关系)
ll 会把 .文件都会展示出来
ls
cd 进入目录
ps aux 查看进程信息
ps -ef 选项表示选择所有进程进行打印
ps -ef | grep java 常用来检查spring启动占用的pid
nohup java -jar jar包名 &
堆——new出来的对象就是在堆里面;对象中持有的非静态成员变量也就是在堆里
栈——本地方法栈,虚拟机栈,方法的调用关系和局部变量
程序计数器——用来存储下一条要执行的java指令地址
元数据区——以前也叫“方法区”。我们写的代码,由javac转化为字节码,再由jvm把字节码加载到内存中放到元数据区(方法区)中,程序就按照元数据中记录的字节码依次执行——“元数据”
例如:文件大小、位置、拥有者、修改时间、
类加载——指java进程运行时,把.class文件从硬盘读取到内存,并进行校验解析的过程
类加载过程 :
加载——找到硬盘上的.class文件,打开文件,读取文件内容(二进制数据)
验证——确保读取到的数据内容是合法的.class文件(字节码文件)
准备——在内存上给类对象申请一块内存空间
解析——针对字符串常量进行处理——JVM将常量池中的符号引用替换为直接引用的过程,也就是初始化常量的过程
初始化——针对对象完成后续的初始化
申请到的内存的生命周期,会跟随整个进程。比如说进程还在执行,但是进程当中某些变量不会在使用了,这个时候它在占用内容空间就不合适了,这一点对服务器程序非常的不好。
jvm内部有一个扫描线程类,扫描到这个它不在使用这块内存了,就会对这块内存空间进行回收
如果有多个引用指向同一块内存空间的话,怎么识别它是一个垃圾呢?
第一种引入计数机制——给对象安排一个额外的空间,空间里保存当前这个对象有几个引用。引用为0的时候就进行回收,但是计数器本身会占用内存空间
第二种:JVM中使用的就是可达性分析
在代码中,会定义许多变量,JVM中存在扫描线程,会对已有的这些变量进行遍历,尽可能的去访问到更多的对象—— 能被访问到的对象,就不会被当做垃圾,访问不到的对象,自然就是垃圾。
第三种:内存对半
第四种:复制算法
这样可以规避内存碎片,但是内存空间少了一半
优化:对垃圾进行标记,再把非垃圾对象整体往前移动(搬运的成本比较高)
分代回收
伊甸区,生存区,老年区
要想达成真正的连接,首先先要在内核层面上达成连接,然后调用这个accept方法,达成应用层面上的连接。
首先tcp报头中有一块是六位标志位——syn(同步报文段)ack(应答文段)
,服务器在发送②③的时候,把TCP数据报包中的六位标志中ask和syn中的值都置为1,就达到了一起发送的效果
好处就是节约了网络带宽资源,网络带宽在网络通信当中是一种非常昂贵的资源
目的:目的是为了让通信双方都能保存对方的相关信息
好处:确认通讯路径是否畅通
就像你下班回家,得先看一下堵不堵车,不堵车的话就开车回家(路径通畅),堵车的话,不回了,加班!
6个标志符中的FIN——结束报文段
那么这里的②③步骤是否也能“合并”呢?
答案是:可以合并,但是不能100%的合并——“如合~”
如果②③两者发送的时间间隔很长,那么就不能合并
======================================================================
listed(译为:已登录的)表示:服务器这边已经建立好了ServerSocket,并且绑定好了端口号,随时准备接收客户端的连接
ESTABLISHED状态:客户端和服务器已经建立完毕(三次握手完了)
close_wait(译为:关闭等待)谁被断开连接,谁进入close_wait状态——服务器收到后发送ACK应答报文后进入close_wait状态。这个状态是比较难观察到的,因为服务器发送ACK和FIN的时间间隔极短作用就是阻塞等待客户端数据请求
TIME_WAIT谁主动断开连接,谁进入TIME_WAIT状态,服务器返回给客户端ASK和FIN,客户端收到返回ASK应答后,进入TIME_WAIT状态。给了一小部分时间展示容错率
情境引入——运营商劫持
引入对称加密,其中很重要的一个东西就是密钥,类似于摩西密码的密码表,正常对明文进行加密,通过密钥进行解密,就可以拿到明文
但是在传递密钥的过程中,可能会被黑客劫持密钥,那么这样我们的明文就会被泄露
解决方式也很简单就是对我们的密钥进行加密,让黑客拿不到我们的密钥
===================================================
引入一对概念——公钥和私钥,公钥由客户端持有,私钥由服务器持有,私钥可以对公钥进行解密
中间人模拟攻击
解决问题的方式也很简单———让客户端能够识别出我拿到的是服务器给的公钥不是模仿出来的公钥
==========================================================
引入三方机构进行加密
证书当中的内容就包括公钥,公正机构的信息,申请时间呀等等,对这些字符串进行某种算法生成一个校验和。客户端拿到这个证书过后,会对这些字符串在计算一次校验和,跟证书上的校验和进行比较。
引入校验和,udp面向数据报进行传输,在报头给它单独给一块空间放校验和,报文传到对端后再计算一次校验和进行对比看是否一致。
应答报文机制,对于每一次交互,都要给对端反馈类似于ack这种我收到你给我发送的信息了
超时重传机制:对于丢包问题,引入超时重传机制,对端久久没有给我反馈的话,我就在传送一次数据
引入网络反馈机制
拥塞控制:慢启动,试探一下当前通讯路径是否畅通
流量控制:接收方及时反馈它的一个接收能力,避免一口吃一个胖子
TCP(传输控制协议):HTTP 通常运行在 TCP 之上。TCP 为 HTTP 提供可靠的、面向连接的字节流服务
IP(网际协议):是 TCP/IP 协议族的核心协议之一,用于在网络中进行数据包的路由和寻址。HTTP 数据通过 TCP 封装后,会进一步被封装在 IP 数据包中,
请求格式:
首行——方法,URL(后面会讲到),版本号
请求头——报头中包含了很多键值对
空行——请求头最下面会有一个空行,这个空行表示结束标记
载荷部分——HTTP载荷部分
响应格式:
首行——版本号,状态码,状态码的描述
响应头:键值对(同请求
空行
载荷部分
http设计的初衷就是为了能够快速的进行超文本传输,每次的请求得到响应之后,http连接就结束了,但是呢我们的http中有这个Cookie和session机制保证了每次请求,浏览器都可以识别获取到对端的信息,所以无连接也问题不大
并且我们的http也可以建立在tcp之上,tcp的可靠传输和有连接特性也可对http起到一定的保证作用
延时接收机制,服务器端会有一块缓存区,客户端每次的请求会先被放到缓存当中,等攒一波之后服务器在进行读取,但是就会出现一个问题,无法辨认包与包之间的界限,
1:分隔符标志,引入序号和确认序号,这在tcp的报头当中也是存在的,
2:在报头专门分配一块区域,说明载荷的长度
1. 项目测试的总体流程
1.1 需求分析与测试计划
1.2 设计测试用例
1.3 测试环境搭建
1.4 执行测试
1.5 回归测试
1.6 性能与压力测试
1.7 安全性测试
如 SQL 注入、
1.8 验收测试与发布
2. 测试方法论
2.1 黑盒测试(Black-box Testing)
2.2 白盒测试(White-box Testing)
2.3 灰盒测试(Gray-box Testing)
4. 测试报告与总结
测试完成后,必须生成详细的测试报告,包含以下内容:
等价类——有效的或无效的划分成若干个等价类,比如说:输入整数的文本框时,输入范围划分为正整数、负整数、零和非整数。挑几个具有代表性的用例
边界值法——取值范围在 1 到 100 之间的整数输入框,边界值就是 1、100 以及紧邻边界的数值,如 0、2、99、101 等。通过测试这些边界值,可以发现一些在边界附近可能出现的错误
判定表法
正交法
场景法——电商购物流程、支付时网络中断、购物车中商品超过库存等情况,
错误猜测法——凭测试的经验了,我学后端的知识也有这方面的原因
首先是引入Selenium库和webdrivermanager驱动
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.5.3</version>
</dependency>
/**
* 创建驱动对象
* @return
*/
public static WebDriver createDriver(){
if(driver == null){
WebDriverManager.chromedriver().setup();
ChromeOptions options = new ChromeOptions();
options.addArguments("--remote-allow-origins=*");
driver = new ChromeDriver(options);
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
}
return driver;
}
driver.findElement(By.cssSelector("#su")).click(); 元素的定位——By.cssSelecttor和xpath,
.sendKeys .clear(); getText()
getTitle() getScreenshotAs屏幕截图
Thread.sleep()
implicitlyWait()隐式等待
driver.navigate().back(); driver.navigate().forward(); driver.navigate().refresh();
喜欢这种观察的感觉,学那么多开发的知识也是为了我能更好的做测试,了解开发它的一种逻辑思维,更好的对整个项目去宏观的掌控。
火影忍者里面有个人物叫鹿丸,这个人物的特点就是非常善于观察,也是鸣人的军师
单元测试,一般是对我们的单个函数体或者单个方法进行测试。
同步接口在调用时,客户端会等待服务器处理请求并返回响应后才会继续执行后续操作
功能测试,(接口的功能是否是正常的)性能测试(响应时间啊),安全性测试(sql注入攻击啊)
异步接口在调用时,客户端发送请求后不会立即等待服务器返回响应,而是继续执行后续操作,服务器处理完请求后会通过某种方式(如回调、消息队列)通知客户端。
自动化测试——Selenium测试
8:敏捷模型
三类重要的角色
产品经理,项目经理,开发团队
前期,产品经理负责收集需求,放到我们的需求池中,发布计划会议(评估哪些需求是合理的,哪些需求是不合理的),迭代计划会议(主要就是明确项目的模块的分工),在迭代周期之内,每天召开例会,及时反馈每天做了什么,遇到什么问题,及时解决。产出可交付软件,演示会议(产品还有哪些问题需要优化),回顾会议(对于这一次迭代,有哪些方面做得好,哪些需要再优化)
(1)正在执行的程序,每一个进程都有一块独立的内存空间,频繁创建和销毁
(2)多个进程不能指向一块同一块内存空间,多个线程可以指向同一块内存空间, pcb里有内存指针
(3)进程更注重我们的资源分配,线程更注重于执行调度
(4)线程缺点①数量问题②优先级问题,多线程安全③异常,打架
(1)继承Thread类重写run方法
(2)类实现Runnable接口,重写run方法,但是还是要用到Thread类
(3)实例化Thread类+匿名内部类,重写run方法
(4)实现实例化Thread,参数中new一个Runnable,在重写run方法
(5)实例化Thread类,参数中写lambda表达式
第一种——共享内存:这是一种最直接的进程间通信方式。操作系统会在物理内存中划分出一块共享区域,
使用套接字也就是socket,它不仅可以用于不同主机上的进程之间的通信,也可以用于同一主机上不同进程之间的通信。通过套接字,进程可以在网络上发送和接收数据,实现资源共享和交互。
lambda匿名内部类获取外部的成员变量,final或者是一个事实final
决定谁先结束,比如在排队买票,有个人插队到我前面,我只能等他买完票,我才能买票。
获取到当前线程的引用(Thread引用) (1)如果继承的是Thread类,那么可以用this拿到线程的引用
(2)如果是Runnable或者lambda的方式创建线程,this不能指向Thread对象了,那么此时this就不可以用了,就只能使用Thread.currentThread。
(1): NEW 创建了Thread对象,但是还没有调用start方法,线程还没有运行起来
(2):TERMINATED terminated终止状态,一个线程已经执行完毕,但是Thread对象还存在,
(3):RUNNABLE runnable状态,线程正在cpu上运行,或者准备就绪,随时可以上cpu运行
(4):TIME_WAITING time_waiting状态, 多为sleep和join引起的带有一定时间的阻塞等待
(5):WAITING waiting状态,(线程死等),多为wait和join引起
(6):BLOCK
block状态,由锁竞争引起的阻塞
思路——进程讲到线程讲到线程池,项目中也用到了线程池(多线程)(多个线程修改同一变量),(线程的操作非原子性)安全问题加锁,产生死锁,产生死锁的四个条件,破坏这个循环条件
是线程的“抢占式执行和随机调度”
多个线程可以修改同一变量
是上述多线程修改变量这一操作并不是“原子性”的,而是可拆分的
t1,t2双线程的运行下,可能同一次读取操作中,t1和t2都读取到的是没有自增的数
可以通俗的理解,本来t1由数字1自增后到2,t2读取的应该是2,然后自增到3.
但是如果t2 在 t1把自增后的2 save回寄存器中 之前 读取的话 t2读到的就是1,最后只能自增到2
(可以理解成被覆盖了)
是线程的“抢占式执行和随机调度”
多个线程可以修改同一变量
是上述多线程修改变量这一操作并不是“原子性”的,而是可拆分的
解决方式——使用synchronized关键字加锁,举count++这个例子非常好,本质上就是把操作原子化
(五个滑稽老铁相当于五个线程,五根筷子相当于五把锁)有五个滑稽老铁一起吃面条,每个滑稽间放了一根筷子,比如一号滑稽老铁想要吃面条,就必须同时拿起1、5两根筷子,滑稽老铁吃面条的时候,其它人不能硬抢筷子。
滑稽老铁除了吃面条,就是放下筷子思考人生(线程不工作),由于每位哲学家什么时候吃面条,什么时候思考人生是不确定的(线程的随机调度),所以大部分情况下,筷子是够用的。
所有人都想吃面条,同时拿起左手边的筷子,此时想拿右手边的筷子时就g了(没人吃到面,也没人释放筷子,这就成了一个死锁了)
======================================================
产生死锁四个条件
①互斥性(获取锁)获取锁的过程是互斥的,一把锁只能被一个线程获取,另一个线程想要获取同一把锁就必须阻塞等待
②不可抢占性——一个线程拿到了锁,除非这个线程主动解锁,否则不会被别的线程强行把锁给抢走
③保持性——一个线程想获取第二把锁,那么第一把锁依旧还是存在的,不会消失
④循环等待
=========================================================
解锁思路
破坏这四个特性中任何一个即可
设置拿起规则,先拿编号小的筷子,再拿编号大的筷子
乐观锁和悲观锁 在加锁过程中,预估发生锁冲突的概率小,降低加锁的工作量,加锁的效率就提高了,安全系数不高;在加锁过程中,预估发生锁冲突的概率大,提升加锁的工作量,加锁的效率就下降了,但是安全系数高 轻量级锁和重量级锁 在乐悲观的基础上延伸出来的 乐/悲观锁是加锁前对没有发生的事情的预估 轻/重量锁是加锁后对结果的评价 整体上来说,两者都是在对一件事情进行描述
自旋锁 自旋锁是轻量级锁的一种实现,也是乐观锁,通过与一个while循环搭配,如果获取到锁,那就结束循环;如果没有获取到锁,不会阻塞放弃cpu,而是继续下一次循环,直到获取到锁 (1)使用场景:锁冲突不激烈 (2)优点:其它线程一旦释放锁,就能快速获取到锁 (3)缺点:会占用消耗大量的cpu资源 挂起等待锁 挂起等待锁是重量级锁的一种实现,也是悲观锁,锁释放后并不能第一时间获取到锁,而是要通过操作系统的内核进行调度去获取锁,这个等待的过程时间较长。 (1)使用场景:锁冲突激烈 (2)优点:在内核调度的等待时间中,cpu可以做别的事情,即降低了cpu的资源消耗 (3)缺点:不能第一时间获取到锁
普通互斥锁和读写锁 1:普通互斥锁 与synchronized相似,可以进行加锁和解锁 2:读写锁 (0)知识联系 想想之前文章写到的MySQL事务处理——三读 “脏读”——给写加锁(写的时候不能读) “不可重复读”——给读加锁(读的时候不能写) “幻读”——读写都加上锁,并行事务串口化 (1)此处解释 读锁和读锁之间,不会发生锁冲突(不会阻塞) 写锁和写锁之间,会发生锁冲突(会阻塞) 读锁和写锁之间,会发生锁冲突(会阻塞) (2)总结 一个线程加读锁的时候,另一个线程只能读,不能写 一个线程加写锁的时候,另一个线程只能写,不能读
公平锁和非公平锁 1:两者对比 我们知道线程遵守“随机调度”的原则,所以在在加锁过程中就产生了“锁竞争”这一现象,在“Java”中规定公平就是遵守“先来后到”这一原则,synchronized本身就是非公平锁——一旦解锁,下一个加锁的线程是无法确定的。 所以我们引入队列,记录每个线程的顺序,依次加锁,实现“公平锁”
A线程拿上了这把锁,不是真正意义上的加锁(假加锁),而是让这把锁对A线程有一个(轻量)标记,如果有其他的线程竞争也想要拿上这把锁,那A就会先一步加锁(真加锁)。
通过“自旋锁”的方式实现,synchronized内部也会统计有多少个线程在“锁竞争”,因为一旦超过某一个线程数量限制,大量的自旋锁会非常消耗cpu资源,此时就会升级为“重量级锁阶段”
优点:其他线程一旦释放锁,就能快速拿到锁
缺点:非常消耗cpu资源
承接上文,此时线程放弃自旋锁,进入“阻塞等待”,当解锁后,系统在随机唤醒线程进行加锁。
6个作用域+5个生命周期