答:对比面向过程,是两种不同的处理问题的角度;面向过程注重事情的每一个步骤和顺序,面向对象更注重事情有那些参与者(对象)、及各自需要做什么。
面向对象有:封装:封装的意义,在于名确标识出外部所使用的成员函数和数据项;
继承:继承基类的方法,并做出自己的改变和扩展;
多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。包含有继承
和重写。注意:无法调用子类的特有的功能。
3.1. JDK:Java开发环境,由JRE和Java工具组成(javac、java、jconsole)
3.2. JRE:Java运行时环境,由JVM(bin)和类库(lib)组成
3.3. JVM:Java虚拟机
4.1. ==:对比栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址
4.2. equals:object中默认的是==比较,通常会重写。String类重写之后比较两个字符串的内容。
5.1、最终的意思,简述final作用:
修饰类:表示类不可以被继承;
修饰方法:表示方法不可以被子类覆盖,但是可以重载;
修饰变量:表示变量一旦被赋值就不可以更改它的值。
5.2、为什么局部内部类和匿名内部类只能访问局部final变量?
首先需要知道的一点是:内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。
将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?
就将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。使得局部变量与内部类内建立的拷贝保持一致。
简单来说,就是局部变量和内部类成员变量保持一致,用final来做一个约定。
6.1、String是final修饰的,不可变,每次操作都会产生新的String对象。
6.2、StringBuffer和StringBuilder都是在原对象上操作;stringBuffer是线程安全的,StringBuilder线程不安全;StringBuffer方法都是synchronized修饰的;性能: StringBuilder > StringBuffer > String。
6.3、场景:经常需要改变字符串内容时使用后面StringBuffer和StringBuilder;
优先使用StringBuilder,多线程使用共享变量时使用StringBuffer。
重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。方法返回值跟重载无关:
public int add(int a, String b);
public String add(int a,String b);
//编译会报错
重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
初级:
中高级:
10.1、hashCode介绍
hashCode()的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定 该对象在哈希表中的索引位置。hashCode()定义在JDK的Object.java中,Java中的任何类都包含有hashCode()函数。散列表存储的是键值对(key-value),它的特点是:能根据"“键"快速的检索出对应的“值””。这其中就利用到了散列码!(可以快速找到所需要的对象)
10.2、为什么要有hashCode
以"Hashset如何检查重复"为例子来说明为什么要有hashCode:
对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有值,如果没有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals ()方法来检查两个对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。
11.1、ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(需要创建大量的node对象)
11.2、LinkedList: 基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlOf对list进行了遍历,当结果为空时会遍历整个列表。
区别:
底层实现:数组+链表实现
jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在
数组扩容
JDK1.7:
JDK1.8:
读操作无锁:
java中的编译器和解释器:
采用字节码的好处:
双亲委派模型的好处:
GC Roots的对象有:
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活
每个对象只能触发一次finalize()方法
由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。
1.锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。
⒉.等待池
当我们调用wait ()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify ()或notifyAll()后等待池的线程才会开始去竞争锁,notify ()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中。
yield () 执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
join () 执行后线程进入阻塞状态,例如在线程B中调用线程A的join (),那线程B会进入到阻塞队列,直到线程A结束或中断线程
不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的
堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。
在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。
目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。
在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。
Thread和Runnable的实质是继承关系,一个是继承一个是实现接口。没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
可理解后台线程,不用管它。所有用户线程退出,它也会自动退出。
守护线程 :为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆;
守护线程类似于整个进程的一个默默无闻的小喽喽;它的生死无关重要,它却依赖整个进程而运行;哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了;
注意:由于守护线程的终止是自身无法控制的,因此千万不要把lO、File等重要操作逻辑分配给它;因为它不靠谱;
作用:GC垃圾回收线程:就是一个经典的守护线程。
应用场景:
(1)来为其它线程提供服务支持的情况;
(2)或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。
原理:
每一个Thread对象均含有一个ThreadLoca1Map类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值。
ThreadLocalMap由一个个Entry对象构成。
Entry继承自weakReference>,一个Entry由ThreadLoca1对象和object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。
当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
使用场景:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离。
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
Spring框架在事务开始时会给当前线程绑定一个Jdbc connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离。
原因:
内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果严重,无论多少内存,迟早会被占光,不再会被使用的对象或者变量占用的内存不能被回收。就是内存泄露。
Threadlocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
避免方法:
ThreadLocal正确的使用方法
每次使用完ThreadLocal都调用它的remove()方法清除数据。
将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。
串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着
并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。
并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行
原子性
原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。2个操作必须全部完成。
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
有序性
虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。
1、降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。
Solr和ElaticSearch都是基于Lucene的Java搜索引擎服务器程序
Solr:
更新索引比较较慢搜索效率比较高
适用于:不需要实时搜索的项目,例如:电商类的项目
ElaticSearch:
更新索引比较快搜索效率比较低
他是为分布式项目而生
适用于:需要实时搜索的项目,例如:新闻类项目,博客。。 o o o
有五种,分别是:String(字符串)、hash(哈希)、list(列表)、set(集合)
zset(sorted set:有序集合)。
区别:
1、Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等;
2、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储;
3、虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘;
4、过期策略–memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10;
5、分布式–设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从;
6、存储数据安全–memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化);
7、灾难恢复–memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复;
8、Redis支持数据的备份,即master-slave模式的数据备份;
9、应用场景不一样:Redis出来作为NoSQL数据库使用外,还能用做消息队列、数据堆栈和数据缓存等;Memcached适合于缓存SQL语句、数据集、用户临时性数据、延迟查询数据和session等。
使用场景:
1、如果有持久方面的需求或对数据类型和处理有要求的应该选择redis。
2、如果简单的key/value 存储应该选择memcached。
Spring 是一个“引擎”;
Spring MVC 是基于Spring的一个 MVC 框架 ;
Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。
1. 作用:
2. 第二问
轻量级的开源的J2EE框架。它是一个容器框架,用来装javabean (java对象),中间层框架(万能胶)可以起一个连接作用,比如说把Struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简洁。
Spring是一个轻量级的控制反转(loC)和面向切面(AOP)的容器框架
–从大小与开销两方面而言Spring都是轻量级的。
–通过控制反转(IoC)的技术达到松耦合的目的
–提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发–包含并管 理应用对象(Bean)的配置和生命周期,这个意义上是一个容器。
–将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架。
系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。
当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。
日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
**AOP:**将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情
ioc容器:
实际上就是个map (key,value),里面存的是各种对象(在xml里配置的bean节点、@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map里。
控制反转:
依赖注入:
“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
ApplicationContext是BeanFactory的子接口
ApplicationContext提供了更完整的功能:
①继承MessageSource,因此支持国际化。
②统一的资源文件访问方式。
③提供在监听器中注册bean的事件。
④同时加载多个配置文件。
⑤载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
1、解析类得到BeanDefinition
2、如果有多个构造方法,则要推断构造方法3、确定好构造方法后,进行实例化得到一个对象
4、对对象中的加了@Autowired注解的属性进行属性填充
5、回调Aware方法,比如BeanNameAware,BeanFactoryAware6、调用BeanPostProcessor的初始化前的方法
7、调用初始化方法
8、调用BeanPostProcessor的初始化后的方法,在这里会进行AOP(动态代理)
9、如果当前创建的bean是单例的则会把bean放入单例池
10、使用bean
11、Spring容器关闭时调用DisposableBean中destory()方法
global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在
global-session中。全局作用域与Servlet中的session作用域效果相同。
不是线程安全的。解释如下:
Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。
如果Bean是有状态的那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域把"singleton"改为"protopyte’这样每次请求Bean就相当于是new Bean()这样就可以保证线程的安全了。
有状态就是有数据存储功能
无状态就是不会保存数据 controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。
Dao会操作数据库Connection,Connection是带有状态的,比如说数据库事务,Spring的事务管理器使用Threadlocal为不同线程维护了一套独立的connection副本,保证线程之间不会互相影响(Spring是如何保证事务获取同一个Connection的)
不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。
spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
实现了FactoryBean接口的bean是一类叫做factoryil的bean。其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getobject()方法,所以返回的不是factory这个bean,而是这个bean.getOjbect()方法的返回值。
spring对单例的实现: spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。
spring定义了一个适配接口,使得每一种Contro11er有一种对应的适配器实现类,让适配器代替contro11er执行相应的方法。这样在扩展Contro11er时,只需要增加一个适配器类就完成了SpringMVc的扩展了。
Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有wrapper,另一种是类名中含有Decorator。
切面在应用运行的时刻被织入。一般情况下,在织入切面时,AoOP容器会为目标对象创建动态的创建一个代理对象。SpringAOP就是以这种方式织入切面的。
织入:把切面应用到目标对象并创建新的代理对象的过程。
spring的事件驱动模型使用的是观察者模式,Spring中0bserver模式常用的地方是Listener的实现。
Spring框架的资源访向Resource接口。该接口提供了更强的资源访问能力,Spring框架本身大量使用了Resource接口来访问底层资源。
数据库的配置隔离级别是Read commited, 而iSpring配置的隔离级别是Repeatable Read,请问这时隔离级别是以哪一个为准?
以spring配置的为准,如果spring设置的隔离级别数据库不支持,效果取决于数据库
spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有如下几种
开启自动装配,只需要在xml配置文件中定义"autowire"属性。
autowire属性有五种装配的方式:
spring是一个lOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等
springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端
springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制) 、redis、mongodb、es,可以开箱即用
Handler:也就是处理器。它直接应对着MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个Handler,只要可以实际处理请求就可以是Handler
Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。
@lmport + @Configuration + Spring spi
自动配置类由各个starter提供,使用@Configuration +@Bean定义配置类,放到META-INF/spring.factories下
使用Spring spi扫描META-INF/spring.factories下的配置类
使用@lmport导入自动配置类
优点:
1、基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
2、与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
3、很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
4、能够与Spring很好的住成;
5、提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
缺点:
1、SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
2、SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行 查询时遍历整张表。索引的原理:就是把无序的数据变成有序的查询
查询更快、占用空间更小
执行计划就是sql的执行查询的顺序,以及如何使用索引查询,返回的结果集的行数
事务基本特性ACID分别是:
隔离性有4个隔离级别,分别是:
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。
在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。
慢查询的优化首先要搞明白慢的原因是什么?是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的,
多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到自己特定版本的数据,版本链
MCC只在READ COMMITTED和REPEATABLE READ两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容,因为READ UNCOMMITTED 总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁。
mysql主从同步的过程:
Mysql的主从复制中主要有三个线程: master (binLog dump thread)、slave(I/O thread 、 SQL thread), Master—条线程和Slave中的两条线程。
由于mysal默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。
MylSAM:(主要用于查)
RDB: Redis DataBase
在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
AOF: Append Only File
以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录
AOF文件比RDB更新频率高,优先使用AOF还原数据。
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了优先加载AOF
Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的亳秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
Redis中同时使用了惰性过期和定期过期两种过期策略。
Redis基于Reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器file event handler。这个文件事件处理器,它是单线程的,所以Redis 才叫做单线程的模型,它采用IO多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。
单线程快的原因:
1)纯内存操作
2)核心是基于非阻塞的IO多路复用机制
3)单线程反而避免了多线程的频繁上下文切换带来的性能问题
1、缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:
2、缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成教据库短时间内承受大量请求而崩掉。
解决方案:
3、缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
全量复制:
(1)主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘lO的
(2)主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
(3)从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行bgrewriteaof,也会带来额外的消耗
部分复制(增量复制):
1.复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset
⒉.复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出(FIFO)队列作为复制积压缓冲区,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
3.服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。从节点Redis断开重连的时候,就是根据运行ID来判断同步的进度:
CAP理论:consistency (一致性)、Availability (可用性)、Partition Tolerance(分区容错性)
consistency (一致性):
即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
Availability (可用性):
即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
Partition Tolerance(分区容错性):
即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对于用户而言并没有什么体验上的影响。
BASE是Basically Available (基本可用) 、Soft state(软状态)和Eventually consistent(最终一致性)
BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
基本可用:
·响应时间上的损失:正常情况下,处理用户请求需要0.5s返回结果,但是由于系统出现故障,处理用户请求的时间变为3 s.
·系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用。
软状态:数据同步允许一定的延迟
最终一致性:系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,不要求实时
1、轮询法
将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。
2、随机法
通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,
其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。3、源地址哈希法
源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。
4、加权轮询法
不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。
5、加权随机法
与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。
6、最小连接数法
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前
积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。
类型:
1、采用无状态服务,抛弃session
2、存入cookie(有安全风险)
3、服务器之间进行Session同步,这样可以保证每个服务器上都有全部的Session信息,不过当服务器数量比较多的时候,同步是会有延迟甚至同步失败;
4、IP绑定策略
使用Nginx(或其他复杂均衡软硬件)中的IP绑定策略,同一个IP只能在指定的同一个机器访问,但是这样做失去了负载均衡的意义,当挂掉一台服务器的时候,会影响一批用户的使用,风险很大;
5、使用Redis存储
把Session放到Redis中存储,虽然架构上变得复杂,并且需要多访问一次Redis,但是这种方案带来的好处也是很大的:
RPC:在本地调用远程的函数,远程过程调用,可以跨语言实现httpClient
RMI:远程方法调用,java中用于实现RPC的一种机制,RPC的java版本,是2EE的网络调用机制,跨JVM调用对象的方法,面向对象的思维方式
uuid
1,当前日期和时间时间戳
2,时钟序列。计数器
3,全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
优点:
代码简单,性能好(本地生成,没有网络消耗),保证唯一(相对而言,重复概率极低可以忽略)
缺点:
数据库自增序列
Leaf-segment
采用每次获取一个ID区间段的方式来解决,区间段用完之后再去数据库获取新的号段,这样一来可以大大减轻数据库的压力
基于redis、mongodb、zk等中间件生成
雪花算法
需要这个锁独立于每一个服务之外,而不是在服务里面。
数据库:利用主键冲突控制一次只有一个线程能获取锁,非阻塞、不可重入、单点、失效时间
zookeeper分布式锁:
Redis分布式锁:setNX,单线程处理网络请求,不需要考虑并发安全性所有服务节点设置相同的key,返回为0、则锁获取失败
删除锁:判断线程唯一标志,再删除
可重入性及锁续期没有实现,通过redisson解决(类似AQS的实现,看门狗监听机制)
前后端分离项目跨域问题是不可避免的。通常情况下前端由React、Vue等框架编写,通过ajax请求服务端API,传输数据用json格式。
不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的
**堆:**是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。
**栈:**是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。
Thread和Runnable的实质是继承关系,一个是继承一个是实现接口。没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
可理解后台线程,不用管它。所有用户线程退出,它也会自动退出。为所有非守护线程提供服务的线程。
作用:GC垃圾回收线程:就是一个经典的守护线程。
原理:
首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。
因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法。
使用场景:
1、进行事务操作,用于存储线程事务信息。
2、数据库连接,Session会话管理。
1、降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。
线程池中阻塞队列的作用?为什么是先添加列队而不是先创建最大线程?
第一问
第二问
串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着
并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。
并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行
虽然两者都是用来暂停当前运行的线程,但是 sleep() 实际上只是短暂停顿,因为它不会释放锁,而 wait() 意味着条件等待,这就是为什么该方法要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。
不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。
先弄明白生产者与消费者的关系。生产者、消费者是两个实体对象,生产者生产物品,消费者消费物品。如果在生产者中定义生产的流程,在消费者中定义消费的流程,两个对象就需要彼此引用,这样依赖性太高,而且实际上性能也不高,这个时候就需要一个缓冲器,一个中间对象,我们把它形象地称之为“仓库”。生产的物品放入仓库,消费的物品从仓库中取出,这样生产者和消费者就能够取消两者之间的引用,直接通过仓库引用来同步状态,降低耦合。
接着我们来看一下生产者、消费者问题的两种类型,一种是使用某种机制来保护生产者和消费者之间的同步,另一种则与Linux中的管道思路相似。相对来说第一种类型的处理方式更为常规,实现方式大致分为三种:经典的wait(),notify()方法、await(),signal()方法以及使用阻塞队列(BlockingQueue)的方法。
具体步骤:
1、 首先用户发送请求到前端控制器,前端控制器根据请求信息(如 URL)来决定选择哪一个页面控制器进行处理并把请求委托给它,即以前的控制器的控制逻辑部分;图中的 1、2 步骤;
2、 页面控制器接收到请求后,进行功能处理,首先需要收集和绑定请求参数到一个对象,这个对象在 Spring Web MVC 中叫命令对象,并进行验证,然后将命令对象委托给业务对象进行处理;处理完毕后返回一个 ModelAndView(模型数据和逻辑视图名);图中的 3、4、5 步骤;
3、 前端控制器收回控制权,然后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染;图中的步骤 6、7;
4、 前端控制器再次收回控制权,将响应返回给用户,图中的步骤 8;至此整个结束。
如有侵权,请联系我删除,谢谢!!