目录
四、在使用ThreadLocal时存在什么风险,有什么注意事项
二十一、ReentrantLock和Synchronized区别
Synchronized的话他底层是基于monitorenter和monitorexit两个指令来实现的,因为每一个对象都拥一个monitor监视器,而线程就会通过Synchronized来获取这个监视器,当监视器被获取时,就会进去锁定状态。
当一个线程第一次获取monitor时,monitor的进入数就会从0变成1,当该线程再进获取时,monitor的进入数会再次加1。
但如果其他线程来获取时,发现monitor的进入数不为0,则会出现堵塞状况,直到进入数为0。
但在java6之前,Synchronized是完全依靠操作系统的互斥锁,会进行一个用户态到内核态的切换,所有的同步锁操作都是一个无差别的重量级操作,非常消耗资源。
所以在java6之后,会引入3种不同的monitor实现,为偏向锁,轻量级锁和重量级锁。
偏向锁其实就是为了在单线程的情况下,完全不用考虑并发情况,所以不会涉及真正的互斥锁。不必浪费资源。但当有另一个线程来访问时,jvm就会从偏向锁转变为轻量级锁。
轻量级锁的话,虽然已经不是单线程的环境了,但这些线程会根据串行的方式来访问同一个加锁对象。但是还是会消耗重复加锁和解锁的性能开销。当正真发生并发情况时,就会切换为重量级锁。
重量级锁他是完全依靠操作系统互斥锁来实现的锁,操作系统的互斥锁会实现线程的切换,从用户态到内核态,所以非常消耗资源。
ThreadLocal是一个线程的局部变量,用于在线程中保存数据。ThreadLocal保存的数据只属于当前线程。每一个Thread都有自己的一个ThreadLocalMap用于数据存储。通过ThreadLocal将数据存入当前线程的ThreadLocalMap中,其中ThreadLocal作为键,存入的数据为值。
ThreadLocal的价值主要在于线程隔离,在该线程存入的数据也只属于该线程,该数据是其他线程不可见的,起到隔离作用。这样的话,在多线程并发环境下,就可以防止当前线程的值被其他线程所修改。相对于加同步锁,能够有效降低性能损失,提高并发的性能。
SpirngBoot自动配置就是不需要我们写代码,所有的配置都由SpringBoot自动完成。
在springboot项目中启动类上都有一个@SpringBootApplication注解,主要是通过这个注解完成。
@SpringBootApplication中包含了三个注解@SpringBootConfiguration注解内部就是一个@Configuration注解,用于标记启动类为配置类。@ComponentScan注解是用于启动时扫描启动类所在包下以及子包下的所有bean的类,并注册到ioc容器中。@EnableAutoConfiguration是完成自动配置的核心注解,通过@Import注解导入AutoConfigurationImportSelector类,通过这个类的selectImports方法读取spring.factories文件,这个文件中包含了可以自动装配的所有类名,然后通过一系列(验证、去重、排除自动装配以及执行监听器等)操作后,将剩余的类名集合返回给IOC容器并将这些组件注册为bean。
SpringBoot最大特点就是他去除了各种xml配置文件,改为application.yml进行统一的配置,并且按照阶梯的模式,简单明了,要使用的对象,则采取注解模式注入,省去大量代码。
可能发生内存泄漏:因为ThreadLocalMap使用ThreadLocal的弱引用为key,如果ThreadLocal没有一个强引用来引用他,那么在下一次GC时势必会回收这个ThreadLocal,从而导致在ThreadLocalMap中存储的一个key为null的值,如果当前线程迟迟不结束的话,那么这个value就会一直存在一条强引用链,从而导致value无法回收,造成内存泄漏。
解决方案:在每次使用完ThreadLocal后都调用他的remove()方法,清除数据。
ipconfig 查看当前ip地址
netstat 监控tcp/ip网络的一个工具,也能查看端口号。
top 查看当前进程信息。(实时更新)
ps 显示当前用户进程,可以通过管道符来过滤。
grep 搜索指定字符串内容。
vim编辑器 用于创建和编写文本。
kill 进程杀死命令。
使用悲观锁,每次访问都要先获取锁。
使用乐观锁,可以设置一个version字段,读取数据时每次将version一起读出,每次更新数据时version+1,并判断数据库中的version值与自己读到的version值是否一致,如果一致,则更新,否则取消更新。
通过请求队列,强制将多线程变为单线程。
通过数据库事务。先更新,在查询,发现库存已经不足,则回滚。
使用redis实现分布式锁。
使用spring事务。
分布式架构就是将一个系统给他拆分成多个,就比如电商系统,可以拆分成功订单系统、商品系统、会员系统、支付系统等等,并且还可以继续拆分。
这样做的好处就是:
可以降低代码的耦合性,对于一个团队来说每个人只需要维护、测试好自己的系统,自己模块,不用担心代码冲突等,能够做到更好团队合作来完成一个项目。
使用事务和回滚:只有当所有操作正常完成才提交事务,否则回滚。
分布式锁:可以保证只有一个线程来对redis和数据库进行写操作。防止并发导致数据数据不一致问题。
定时同步数据:通过一个任务调度来周期性的检查数据是否同步,并将redis中的数据同步至数据库。确保数据的一致性。
发布/订阅模式:可以用于实现数据库与redis的实时更新同步。
socket套接字,他是一个用于网络通信的一个API接口。他的话就是在一个应用程序进行数据传输时,会通过socket来建立一个远程连接,而socket内部是通过Tcp/Ip协议来把数据传输到网络。
一般在java进行Tcp编程时,会用到Socket模型,服务器用ServerSocket来监听端口,客户端通过Socket来连接服务器,服务端用accept()方法来接收并返回Socket实例,双方通过Socket来打开输入/输出流来进行读写数据。
首先volatile是一个关键字,当他作用余一个变量时,可以使这个变量具有可见性和有序性。因为当对一个volatile变量执行写入时,jvm会将对应的共享变量立即刷新到主存,当读取这个变量时,也会从主存中进行读取。有序性是通过happens-before原则来保证的。.
CAS指的是compare and swap,即比较和交换。他的话本质是一种无锁的线程同步解决方案,基于乐观锁思想的操作。就是比如说在你需要更新一个变量时,先看一下这个变量的值并记住,然后在你真正要改时,再次看一下这个变量的值是否和上次一样,如果一样则进行更新,否则说明已经被其他线程修改,则不更新。所以并没有实现加锁操作。
在java中AtomicInteger、concurrentHashMap都是基于CAS机制来实现的。
CAS的其实是通过Unsafe类提供compareAndSwapXXX方法来实现的,底层则是通过这个方法来调用cpu的一个指令cmpxchg。这个指令连续执行,不会被打断,所以可以保证原子性。
缺点:
循环时间长,开销大。
Unsafe的实现中使用了自旋锁机制,如果cas操作失败,则会循环进行操作。如果长时间都不成功的话,则会对cpu有很大的开销。
只能保证一个共享变量的原子操作。
ABA问题。
就是如果线程1想要改变一个值时,先看为A,然后线程2将这个值改为了B,然后又改为了A。当线程1真正修改这个值发现这个值还是A没有被修改,则会继续执行,进行修改。
可以通过AtomicStampedReference类使用compareAndSet方法来解决该问题。
AQS的全名为AbstractQueuedSynchronizer,ReentrantLock就是基于AQS来实现的,她可以说是一种提供原子式管理同步状态、阻塞和唤醒线程等功能,并提供等待队列模型的线程同步框架。
他的核心思想就是,如果请求的资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,并将资源设置为锁定状态,如果资源被占用,则会实现等待机制,就是将获取不到锁的线程加入到等待队列中,由CLH变体的FIFO双向队列,并用一个由volatile修饰的int类型的state来表示其同步状态,通过cas来完成对state的改变,来实现多线程的独占模式(state=0)和共享模式(state>0同一时刻可以多个线程获取同步资源)。
抽象类的话是通过abstract class来定义,接口则是通过interface
抽象类是通过子类用extents来继承,子类只能继承一个父类,接口则是通过implements来实现,类可以实现多个接口
抽象类的话可以有构造方法,接口则没有构造方法
抽象类既可以定义抽象方法也可以定义普通方法,接口只能定义抽象方法,但在java8开始可以用default 来定义默认方法
抽象类的方法可以是任意修饰符,接口则只能是public修饰
ArrayList的底层是Object数组,LinkedList的底层是双向链表
ArrayList主要用于查找和修改,LinkedList主要用于插入和删除
hashmap是线程不安全的,他的底层结构是数组+链表+红黑树,他存储的是键值对,这个键值对会被封装在一个Entry对象中,这个对象中有key、value、next、hash。在一个数组中保存一个个Entry对象(Node
一般的话,hashmap的初始容量是16,当每次存储元素时,都会根据key值来计算出hash值,通过(长度-1)&hash来算出要存放在数组中的位置下标,如果该位置已经有其他元素,则会以链表的方式存储,通过next来指向新添加的元素,形成链表(时间复杂度O(n)。
当链表长度大于8并且数组长度大于64时链表将会转化为红黑树(时间复杂度O(logn))(TreeNode
concurrentHashMap是一个线程安全的且效率比较高的Map,在jdk1.7之前是由分段数组和链表组成,而在jdk1.8改为了数组、链表和红黑树。底层通过Synchronized和cas来实现。
在运行期间能够能够直接操作程序内部的属性和方法,会破坏封装。
比如说,在运行期间获取任意一个对象所属的类,或者是构造任意一个对象,获取或调用任意一个对象的成员变量和方法。
一般的反射用的比较多的都是一些底层框架中,比如spring框架、MyBatis框架。
在JDBC连接数据库时、使用Class.forName(),通过反射来加载数据库的驱动类。
Spring中的IOC和AOP底层也是使用反射实现。
MyBatis框架中Mapper接口代理对象的创建,也是用反射实现。
互斥条件:该资源任意一个时刻只有一个线程占用
请求与保持条件:一个进程因请求资源堵塞时,对以获得的资源保持不放
不剥夺条件:线程获得资源在未使用完之前不能被其他线程强行获取
循环等待条件:若干进程之间形成一种头尾相接的循环等待关系
两者都是可重入锁,自己可以再次获取自己的内部所。
synchronized的话,他是通过底层moniter指令来实现的,reentrantLock则是通过jdkApi
reentrantLock还可以等待可中断,就是可以中断正在等待的线程和可实现公平锁,默认是非公平锁。(公平锁是指先等待的线程先获取锁)synchronized只能是非公平锁
使用场景:
synchronized 适用于:
ReentrantLock 适用于:
- @Override
- public boolean equals(Object otherObject) {
- //1.先比较地址是否相等 这样开销最小
- if (this == otherObject) return true;
- //2.如果比较的对象会null 则直接返回false
- if (otherObject == null) return false;
- //3.检测比较的对象与当前对象是否属于同一个类 不属于则返回false
- if (this.getClass() != otherObject.getClass()) return false;
- //4.转化为特定类型
- MyEqual myEqual = (MyEqual) otherObject;
- //5.最终进行字段的比较
- return myEqual.id == id && Objects.equals(myEqual.name, name);
- }
-
- //6.重写hashcode方法
- @Override
- public int hashCode() {
- //不要忽略,如果这里返回一个常量 将导致所有比较都相等了
- return Objects.hash(id, name);
- }
线程:
NEW(初始状态)、READY(就绪状态)、RUNNABLE(运行状态)、BLOCKED(堵塞状态)、WAITING(等待状态)、TIME_WAITING(超时等待状态)、TERMINATER(终止状态)
线程池:
RUNNING(运行状态)、SHUTDOWN(不在接受新任务)、STOP(不在接受新任务、停止所有任务)、TIDYING(所有任务全部停止)、TERMINATED(终止状态)
首先是线程被创建后处于NEW初始状态,通过start()方法后进入READY就绪状态,等待JVM调度,当获取时间片后,会进入RUNNABLE运行状态,如果执行wait()方法,则会进入WAITING等待状态,需要其他线程通过notify或notifyAll唤醒,也可以通过wait()方法或sleep()方法设置时间参数,来进入TIME_WAITING超时等待状态,等到时间结束就会回到RUNNABLE运行状态,当线程调用同步方法时,没有获取到锁的情况下会进入BLOCKED堵塞状态,线程结束后会进入TERMINATED终止状态。
当调用start()方法后,线程会被放入等待队列,等待JVM调度,当获取CPU时间后,线程会调用run()方法,执行业务逻辑。
start()方法用于启动线程,run()方法则是用于封装业务逻辑代码。当new 一个Thread对象后需要重写run()方法。
Throwable类是所以异常的父类、他一共有两个子类,Error类和Exception类。
Error类一般是系统内部错误,通常是JVM虚拟机异常,如StackOverflowError(栈溢出)和OutOfMemoryError(堆溢出)。
Exception类是运行时异常,Exception中还分为两种异常类型。
一种是RuntimeException或RuntimeException子类,这种是检查型异常,一般需要使用try、catch处理,否则报错。如NullPointerException(空指针异常)、IndexOutOfBoundsExceptin(下标越界异常)。
另一种是Excaption类或Excaption子类,为非检查型异常,不需要处理。如IOException(IO读写异常)、SQLException(SQL异常)。
Lambda表达式他主要就是简写代码,还能进行java性能提升。
一般可以用于替代匿名内部类,比如说新建一个线程就可以new Thread()->().start();
对集合进行迭代,forEach遍历。
还能通过Stream流进行一些过滤、排序等操作。
简单易用:JSON的语法简单,易于理解和编写,可以快速地进行数据交换。
跨平台支持:JSON可以被多种编程语言解析和生成,可以在不同的平台和语言之间进行数据交换和传输。
数据交换格式:JSON是一种标准的数据交换格式,可以在Web应用程序中广泛使用,如前后端数据交互、API接口数据传输等。
轻量级:JSON的数据格式轻量级,传输数据时占用带宽较小,可以提高数据传输速度。
易于扩展:JSON的数据结构灵活,支持嵌套对象和数组等复杂的数据结构,便于扩展和使用。
安全性:JSON数据格式是一种纯文本格式,不包含可执行代码,不会执行恶意代码,因此具有较高的安全性。
面向对象就是万物皆可对象,每个对象都有每个对象的作用,并且对象直接是有相互联系的,每个对象都有自己的属性,方法。
主要分为三个模块
继承:就是子类继承父类,让子类拥有父类的方法和属性,能够减少代码的冗余。
封装:就是隐藏对象的属性和实现细节,只能通过对外开放的方法来获取或修改被隐藏的属性。
多态:是代码有更好的拓展性,一般体现在重写、重载、向上转型(父类实例引用子类对象)、向下转型(将父类转换为子类)
newFixedThreadPool(固定数目线程的线程池):
一般适用于执行长期任务,尽可能少分配长执行。
newCachedThreadPool(可缓存线程的线程池):
一般用于并发量大执行时间短的小任务
newSingleThreadExcutor(单线程的线程池):
一般用于串行执行任务,一个一个执行。
newScheduledThreadPool(定时及周期执行的线程池):
一般用于周期性执行任务场景。
ArrayBlockingQueue(有界队列):数组实现、按FIFO排序。
LinkedBlockingQueue(可设置容量队列):链表实现,FIFO排序,可自己设置容量,不设置则无边界。
DelayedWorkQueue(延迟队列):任务定时周期延迟执行的队列,根据指定的执行时间从小到大排序。
PriorityBlockingQueue(优先级队列):具有优先级的无界队列。
SynchronousQueue(同步队列):不存储元素,每个插入操作必须等另一个线程调用移除操作,否则插入操作一直阻塞。
首先,如果是数组长度是2的幂次方的话,不易产生hash冲突。
一般计算下标时用(n-1)&hash,如果是2的幂次方的话,n-1的最低位就都是1,可以保证计算出的下标分布均匀。
用于对象的序列化,在类上实现Serializable接口
序列化就是将对象转化为字节流的过程
序列化的使用情况:
状态模式重点在各状态之间的切换,从而做不同的事情;
而策略模式更侧重于根据具体情况选择策略,并不涉及切换。
状态模式封装了对象的状态,而策略模式封装算法或策略。
简单工厂的话他又称为静态工厂模式,就是它可以根据传入不同的参数来返回不同的实例。
工厂模式的话,工厂父类负责创建产品的公共接口,工厂子类则是负责生成具体的产品对象。
NonfairSync 类继承了 Sync类,表示采用非公平策略获取锁:每一次都尝试获取锁,不会按照公平等待的原则进行等待,不会让等待时间最久的线程获得锁。
FairSync类也继承了 Sync类,表示采用公平策略获取锁:当资源空闲时,它总是会先判断 sync队列是否有等待时间更长的线程,如果存在,则将当前线程加入到等待队列的尾部,实现了公平获取原则。
对称加密是指加密和解密使用相同的密钥的加密算法。他的原理就是将明文通过密钥进行加密,然后再将加密后的密文发送出去。接受方接受到密文后,再用相同的密钥解密。
非对称加密是指加密和解密使用不同的密钥的加密算法。他的原理的话就是发送方用接收方的公钥加密,发送给接收方,接受用自己私钥解密。
密钥交换算法是在双方不直接传递密钥的情况下完成密钥交换。他的原理就是我们把a看成甲的私钥,A看成甲的公钥,b看成乙的私钥,B看成乙的公钥,DH算法的本质就是双方各自生成自己的私钥和公钥,私钥仅对自己可见,然后交换公钥,并根据自己的私钥和对方的公钥,生成最终的密钥secretKey,DH算法通过数学定律保证了双方各自计算出的secretKey是相同的。
修饰实例方法:synchronized修饰实例方法, 则用到的锁,默认为this当前方法调用对象(this)
修饰静态方法:synchronized修饰静态方法, 则其所用的锁,默认为Class对象(this.getClass())
修饰代码块:synchronized修饰代码块, 则其所用的锁,是某个指定Java对象(自定义对象)
Class对象的话他是JVM用来保存类对象信息的,一般的话在java的编译期间会将源码文件编译成.class 字节码文件,编译器的话也会同时在这个文件中生成Class对象。一般在加载阶段就会通过类加载机制将Class对象加载到内存中。
一般可以通过三种方式获取Class对象:类名.Class、Class.forName()、getClass()。
正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判 断文档中是否包含所需要的词条,是根据文档找词条的过程。
而倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id 获取文档。是根据词条找文档的过程。
Elasticsearch(ES)是一个开源的分布式搜索引擎,它提供了高效的全文搜索、分析和数据可视化功能。在实际应用中,ES的高可用性是非常重要的,因为它通常用于处理大量的数据和请求。
ES高可用性的实现主要依赖于以下三个方面:
分布式架构:
ES采用分布式架构,将数据分散存储在多个节点上,每个节点都可以处理请求和响应。这种架构可以提高系统的可扩展性和容错性,因为当一个节点出现故障时,其他节点可以继续工作,保证系统的正常运行。
数据复制:
ES采用数据复制机制,将数据复制到多个节点上,以保证数据的可靠性和可用性。当一个节点出现故障时,其他节点可以继续提供服务,因为它们都有相同的数据副本。此外,ES还提供了主从复制机制,其中一个节点作为主节点,负责处理写操作,其他节点作为从节点,负责处理读操作。这种机制可以提高系统的性能和可用性。
负载均衡:
ES采用负载均衡机制,将请求分发到多个节点上,以提高系统的性能和可用性。负载均衡可以根据节点的负载情况和网络延迟等因素,动态地调整请求的分发策略,以保证每个节点都能够处理适当的请求量。
String 类型
date 时间类型
复杂类型Array、object、nested
GEO 地理位置类型
XA模式:
他是有一个事务协调者,会通知每个事务参与者去执行本地事务,然后事务参与者执行完后会返回给事务协调者执行状态,如果全部成功则提交事务,如果有一个失败就会所有回滚。
AT模式:
他是每个事务都会记录一个数据快照日志,执行完事务就直接提交,返回事务的状态,如果成功会删除日志,如果失败,则会根据日志回滚到更新前。
在AT模式可能会发生脏写问题,所有引入了全局锁的概念在释放DB锁之前,先拿到全局锁,避免同一时刻有另外一个事务来操作当前数据。
事务1先获取DB锁,保存快照,然后执行sql,然后在获取全局锁,提交事务,释放DB锁,这个时候如果有事务2,则他会获取DB锁,执行sql,在获取全局锁,发现获取失败,会重试默认30次间隔10毫秒,最终还没获取成功则事务回滚。
AT模式与XA模式最大的区别:
XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
XA模式强一致;AT模式最终一致
注:本篇文章都是我自己的理解,可能用词和语句不够严谨,如有错误请评论指正,谢谢!(持续更新中......)