后台开发总结
给自己定一个小目标(把里面的问题吃透)
Part1 复习一下这几个方法:
Integer.toBinaryString(); //位图法,从海量数据中查找
记住Map的两个函数:
Object computeIfPresent(Object key,(key,value)->newValue); //key存在,oldValue!=null,计算新值并覆盖,但是新值为null,删除。
Object putIfAbsent(Object key,Object value); //key对应的value为null,删除
part2 .什么是自动装箱和拆箱(链接)
自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。原始类型byte, short, char, int, long, float, double 和 boolean 对应的封装类为Byte, Short, Character, Integer, Long, Float, Double, Boolean。
自动装箱和拆箱的原理
自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。
Integer类型首先判断i值是否在-128和127之间,如果在-128和127之间则直接从IntegerCache.cache缓存中获取指定数字的包装类;不存在则new出一个新的包装类。
IntegerCache内部实现了一个Integer的静态常量数组,在类加载的时候,执行static静态块进行初始化-128到127之间的Integer对象,存放到cache数组中。cache属于常量,存放在java的方法区中。
通过分析源码发现,只有Double和Float的自动装箱代码没有使用缓存,每次都是new 新的对象,其它的6种基本类型都使用了缓存策略。使用缓存策略是因为,缓存的这些对象都是经常使用到的(如字符、-128至127之间的数字),防止每次自动装箱都创建一此对象的实例。
public class Test {
public static void main(String[] args) {
test();
}
public static void test() {
int i = 40;
int i0 = 40;
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
Double d1=1.0;
Double d2=1.0;
}
//1、这个没解释的就是true
System.out.println("i=i0\t" + (i == i0)); //true
//2、int值只要在-128和127之间的自动装箱对象都从缓存中获取的,所以为true
System.out.println("i1=i2\t" + (i1 == i2)); //true
//3、涉及到数字的计算,就必须先拆箱成int再做加法运算,所以不管他们的值是否在-128和127之间,只要数字一样就为true
System.out.println("i1=i2+i3\t" + (i1 == i2 + i3));//true
//比较的是对象内存地址,所以为false
System.out.println("i4=i5\t" + (i4 == i5)); //false
//5、同第3条解释,拆箱做加法运算,对比的是数字,所以为true
System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));//true
//double的装箱操作没有使用缓存,每次都是new Double,所以false
System.out.println("d1=d2\t" + (d1==d2));//false
part2+: final详解:
1⃣️final修饰成员变量:首先明确成员变量是随着类的初始化或者对象的初始化而初始化的。当类初始化,系统会为该类的类变量分配内存,并分配默认值;当创建对象时,系统会为该对象的实例变量分配内存,并分配默认值。final修饰的变量必须由程序员显示指定初始值,系统不会对final进行隐式初始化。
public class FinalTest {
final int a;
{
System.out.println(a); //报错
}
}
类变量:必须在静态初始化快或者定义该类变量时指定初始值;
成员变量:必须在非静态代码块、构造函数或者声明该实例变量时指定初始值。
public class FinalTest {
final int a;
final int[] array = new int[2];
{
a = 1;
System.out.println(a);
System.out.println(Arrays.toString(array)); //[0,0]
array[1] = 12; //ok
array = null; //报错,引用地址改变了
}
public static void main(String[] args){
FinalTest f = new FinalTest();
}
}
局部变量:系统不会对局部变量进行初始化,必须由程序员显示初始化,可以在定义时指定默认值,也可以不指定,在后面的代码中为final局部变量赋值,但是只能一次。
public class Main {
public void set(){
final int a;
a = 10;
a = 20; //编译不通过
System.out.println(a);
}
public static void main(String[] args) {
new Main().set();
}
}
小心:当使用final修饰基本类型变量时,不能对基本类型变量重新复制,因为基本类型变量不能被改变,但对于引用类型变量,保存的是一个引用,只要这个引用所引用的地址不改变,这个对象里面的内容完全可以改变。
宏变量:1.使用final修饰; 2.在声明该变量时指定初始值; 3.该初始值可以在预编译的时候确定下来。
final变量的本质就是一个宏变量,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。
public class FinalTest {
/*
宏替换:1.使用final修饰;2.在定义的时候声明初始值;3.该初始值可以在编译的时候确定下来;
*/
public static void main(String[] args){
String s = "疯狂java讲义:99";
String s1 = "疯狂java讲义"+":99";
//s1引用的字符串可以在编译时确定下来
System.out.println(s==s1); //true
String s2 = "疯狂java讲义";
String s3 = ":99";
//s4由s2和s3连接得到,由于s2和s3只是两个普通的字符串,编译时不会执行宏替换,所以在编译
//阶段无法确定s4的值。虽然s5使用了final修饰,但是同样无法在编译阶段确定s5的值。
String s4 = s2+s3;
final String s5 = s2+s3;
System.out.println(s==s4); //false
System.out.println(s==s5); //false
//s8中的s6和s7在编译的时候执行了宏替换。
final String s6 = "疯狂java讲义";
final String s7 = ":99";
String s8 = s6+s7;
System.out.println(s==s8); //true
//虽然s10使用了final修饰,但是无法在编译阶段确定s10的值。
String s9 = "疯狂java讲义:" + Integer.valueOf(99);
final String s10 = "疯狂java讲义:" + Integer.valueOf(99);
System.out.println(s==s9); //false
System.out.println(s==s10); //false
}
}
final修饰的方法被子类重写,比如Object中的getClass()方法;
final修饰的类不能被继承,比如java.util.Math类。
不可变类:java提供了8个包装类和String类都是不可变类,不可变类的意思是创建该类的实例后,该实例的实例变量不可修改,Double d = new Double(8.0);//无法修改传入的参数。
不可变类须遵守:1.使用private和final修饰该类的成员变量;2.提供带参的构造方法,初始化成员变量;3.只提供getter方法,不提供setter方法;4.如果有必要,重写hasCode()和equals()方法,应保证两变量的equals()方法相等的hasCode()方法也相等。
part3 .Java并发编程之volatile关键字解析
普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。但是volatile不能保证原子性,原子性指的是一个操作或者多个操作要么全部执行并且执行过程不被其他因素所打断,要不全部不执行。但是对于i++这样的操作,包括读取i的值,加上1,写值三个部分,volatile只保证对于某一个线程的修改其他线程立刻可见。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
Part4 Spring的两种代理(使用的是代理模式)
先说一下AOP的概念,AOP从动态角度考虑程序运行过程,专门用于处理系统中分布于各个模块(不同方法)中交叉关注点的问题,能更好抽离出各个模块的交叉关注点。
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。(java.lang.reflect).
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//target的接口
interface ILog{
void record(String[] args);
}
class Log implements ILog{
@Override
public void record(String[] args) {
for (String arg : args){
System.out.println(arg);
}
}
}
//AOP
class Transaction{
public void transBefore(){
System.out.println("事物开启");
}
public void transAfter(){
System.out.println("事物结束");
}
}
class MyInvokationHandler implements InvocationHandler{
private Object target;
public void setTarget(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Transaction t = new Transaction();
t.transBefore();
method.invoke(target,args); //只用是执行target的方法都会来执行invoke方法
t.transAfter();
return null;
}
}
public class AopTest {
//这种方式相当于代理类和target实现了同一个接口
public static Object proxyInstance(Object target){
MyInvokationHandler invokationHandler = new MyInvokationHandler();
invokationHandler.setTarget(target);
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),invokationHandler);
}
public static void main(String[] args){
ILog target = new Log();
ILog log = (ILog)proxyInstance(target);
log.record(new String[]{"A","B","C"});
}
}
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP;
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换;
如何强制使用CGLIB实现AOP?
(1) 添加CGLIB库,SPRING_HOME/cglib/*.jar
(2) 在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
JDK动态代理和CGLIB字节码生成的区别?
(1) JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2) CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final 。
part5: Spring中两种后处理器
Bean后处理器(接口):
BeanPostProcessor(两个方法:Object postProcessBeforeInitialization(Object bean,String name) 和 Object postProcessAfterInitialization((Object bean,String name))。
容器后处理器:接口:BeanFactoryPostProcessor(PropertyPlaceholderConfigurer PropertyOverrideConfigure)
Part6 maven
<dependencies>
<dependency>
<groupId>junit</groupId> <!--一般是域名反写-->
<artifactId>junit</artifactId> <!--项目名-->
<version>3.8.1</version> <!--Jar版本号-->
<scope>test</scope>
</dependency>
</dependencies>
part7 线程状态转移图
part8 线程和进程的区别
part9 yield和join的区别,执行完join之后放锁么?
wait()和sleep()的区别,执行完这两个方法之后放锁么?
简述:
1.sleep():在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。不推荐使用。
sleep()使当前线程进入阻塞状态,在指定时间内不会执行。
2.wait(): 在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果不在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
3.yield(): 暂停当前正在执行的线程对象。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。yield()只能使同优先级或更高优先级的线程有执行的机会。
4.join(): 等待该线程终止。等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
注意Java并不限定线程是以时间片运行,但是大多数操作系统却有这样的要求。在术语中经常引起混淆:抢占经常与时间片混淆。事实上,抢占意味着只有拥有高优先级的线程可以优先于低优先级的线程执行,但是当线程拥有相同优先级的时候,他们不能相互抢占。它们通常受时间片管制,但这并不是Java的要求。
yield()和join()详解:比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行, 只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还 在别人手里,别人还没释放。如果notify/notifyAll方法后面的代码还有很多,需要这些代码执行完后才会释放锁),调用wait方法的一个或多个线程就会解除wait状态,重新参与竞争对象锁,程序如果可以再次得到锁,就可以继续向下运行。
1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
2)当前线程必须拥有此对象的monitor(即锁),才能调用某个对象的wait()方法能让当前线程阻塞,(这种阻塞是通过提前释放synchronized锁,重新去请求锁导致的阻塞,这种请求必须有其他线程通过notify()或者notifyAll()唤醒重新竞争获得锁)
3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;(notify()或者notifyAll()方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁)
4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程,唤醒的线程获得锁的概率是随机的,取决于cpu调度。
(纠错:在main方法中 通过new ThreadTest(t).start()实例化 ThreadTest 线程对象, 它 通过 synchronized (thread) ,获取线程对象t的锁,并Sleep(9000)后释放,这就意味着,即使main方法t.join(1000)等待一秒钟,它必须等待ThreadTest 线程释放t锁后才能进入wait方法中,它实际等待时间是9000+1000ms❌。不是等待1s,而是main函数拿到锁之后只等待t线程执行1s中,如果1s之后t线程还没执行完,main函数就不等了):
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
t.join(); //使调用线程 t 在此之前执行完毕。
t.join(1000); //等待 t 线程,等待时间是1000毫秒
class RunnableImpl implements Runnable {
public void run() {
try {
System.out.println("Begin sleep");
Thread.sleep(2000);
System.out.println("End sleep");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadTest extends Thread {
Thread thread;
public ThreadTest(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
synchronized (thread) {
System.out.println("getObjectLock");
try {
Thread.sleep(9000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("ReleaseObjectLock");
}
}
}
public class JoinTest {
public static void main(String[] args) {
Thread t = new Thread(new RunnableImpl());
new ThreadTest(t).start();
t.start();
try {
long start = System.currentTimeMillis();
t.join(1000);
System.out.println("join time:"+(System.currentTimeMillis()-start));
System.out.println("joinFinish");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:getObjectLock
Begin sleep
End sleep
ReleaseObjectLock
join time:9005
joinFinish
解析:join方法的源码中调用了wait方法,也就是说main函数调用t.join(1000)时,需要拿到t的锁,但是此时t的锁被synchronized锁住了,所以main方法拿不到就阻塞了,当ThreadTest执行完了之后,main函数才能拿到锁,然后最多等到t执行1秒。
如果把ThreadTest类run()中的synchronized注释掉,
结果:getObjectLock
Begin sleep
join time:1003
joinFinish
End sleep
ReleaseObjectLock
解析:因为没有synchronized握着t的锁,所以main函数在执行t.join(1000)的时候可以获得t线程的锁(对象本身),也就是说main函数等到t线程执行1000毫秒,如果t线程没有执行完,那么主线程也不管了,会正常和t线程竞争。
记住两点,容易混淆的地方:(1)哪个方法调用t.join(),那么该方法就会就会阻塞,等到t线程执行完之后才会继续执行,但是需要注意,这个方法必须拿到t的对象锁;
(2)t.join(1000)的意思是,调用还方法的线程在拿到t对象锁的情况下,最多等待t执行1秒,如果1秒之后t线程还没执行完,那么调用t.join(1000)的那个线程就会到就绪状态。
part9+
0⃣️死锁的产生原因:多个线程循环等待它方占有的资源而无限期僵持下去的局面。
产生死锁的四个条件:
互斥条件:一个资源只能被一个线程使用;
请求保持条件:当一个线程因请求资源而阻塞时,对已获取的资源保持不放;
不剥夺条件:线程已获得资源买未使用完之前,不能强行剥夺;
循环等待条件:若干个线程之间形成头尾相接的资源等待情况。
死锁的小程序:(因join引发的两种死锁的小例子)
public class DeadThread extends Thread{
private Lock sourthLock;
private Lock northLock;
public DeadThread(String name,Lock sourthLock,Lock northLock){
this.setName(name);
this.northLock = northLock;
this.sourthLock = sourthLock;
}
public void run(){
if (this.getName().equals("north")){
try {
northLock.lockInterruptibly(); //<1>
System.out.println("get north north lock");
try {
sourthLock.lockInterruptibly(); //<2>
System.out.println("get north sourth lock ");
}catch (Exception e){
e.printStackTrace();
}
}catch (Exception e){
e.printStackTrace();
}
}
else {
try {
sourthLock.lockInterruptibly(); //<3>
System.out.println("get sourth sourth lock");
try {
northLock.lockInterruptibly(); //<4>
System.out.println("get sourth north lock ");
}catch (Exception e){
e.printStackTrace();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args){
Lock sourthLock = new ReentrantLock();
Lock northLock = new ReentrantLock();
DeadThread t1 = new DeadThread("north",sourthLock,northLock);
DeadThread t2 = new DeadThread("sourth",sourthLock,northLock);
t1.start();
t2.start();
}
}
怎么找到哪里发生了死锁:Java程序死锁以及解决办法
第一种方法:jstack:首先使用jps查看目前的进程id; -> 然后使用jstack -l id号;
第二种方法:
%cd $JAVA_HOME
%bin/jvisualvm //jdk自带的图形化工具
jmap:https://www.cnblogs.com/kongzhongqijing/articles/3621163.html
内存泄露
内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。
Java内存泄露根本原因:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。
造成内存泄漏的几种情况:
1、静态集合类引起内存泄漏
像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
3、监听器
在释放对象的时候却没有去删除这些监听器,增加了内存泄漏的机会。
4、各种连接
比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。
5、内部类和外部模块的引用
内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: public void registerMsg(Object b); 这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。
6、单例模式
不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏。
如果死锁已经发生,如何消除死锁,使系统从死锁状态中恢复过来。消除死锁的几种方式:
1. 最简单、最常用的方法就是进行系统的重新启动,不过这种方法代价很大,它意味着在这之前所有的进程已经完成的计算工作都将付之东流,包括参与死锁的那些进程,以及未参与死锁的进程;
2. 撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。这时又分两种情况:一次性撤消参与死锁的全部进程,剥夺全部资源;或者逐步撤消参与死锁的进程,逐步收回死锁进程占有的资源。一般来说,选择逐步撤消的进程时要按照一定的原则进行,目的是撤消那些代价最小的进程,比如按进程的优先级确定进程的代价;考虑进程运行时的代价和与此进程相关的外部作业的代价等因素;
3. 进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处,并由此点处继续执行,以求再次执行时不再发生死锁。虽然这是个较理想的办法,但是操作起来系统开销极大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。
静态synchronized方法的锁是Java文件对应的Class对象,而非静态synchronized方法的锁是是个实例对象。
--synchronized是java的关键字,而Lock是一个接口;
--synchronized在发生异常时,会自动释放所占有的锁,因此不会导致死锁,而Lock发生异常时,必须手动释放锁lock.unlock(),否则会发生死锁;
--使用synchronized时,等到的线程会一直等待,不能响应中断,也就是说,别的线程不能使用Thread.interrupted()中断,而Lock可以通过lockInterruptibly()方法获取锁,在未获取锁之前通过 线程.interrupt()方法中断该线程;
ReentrantLock的加锁方法Lock()提供了无条件地轮询获取锁的方式,lockInterruptibly()提供了可中断的锁获取方式。通过分析源码可以知道lock方法默认处理了中断请求,一旦监测到中断状态,则中断当前线程;而lockInterruptibly()则直接抛出中断异常,由上层调用者区去处理中断。lock方法会忽略中断请求,继续获取锁直到成功;而lockInterruptibly则直接抛出中断异常来立即响应中断,由上层调用者处理中断。
--使用Lock可以判断当前线程是否获取到了锁,而synchronized却不能;
--使用Lock可以提高多个线程的读写效率;
--synchronized是一种互斥锁,Lock有可重入锁和读写锁。
Lock接口的 线程请求锁的 几个方法:
lock(), 拿不到lock就不罢休,不然线程就一直block。 比较无赖的做法。
tryLock(),马上返回,拿到lock就返回true,不然返回false。 比较潇洒的做法。
带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false。比较聪明的做法。
lockInterruptibly():
先说说线程的打扰机制,每个线程都有一个 打扰 标志。这里分两种情况,
1. 线程在sleep或wait,join, 此时如果别的进程调用此进程的 interrupt()方法,此线程会被唤醒并被要求处理InterruptedException;(thread在做IO操作时也可能有类似行为,见java thread api)
2. 此线程在运行中, 则不会收到提醒。但是 此线程的 “打扰标志”会被设置, 可以通过isInterrupted()查看并 作出处理。
lockInterruptibly()和上面的第一种情况是一样的, 线程在请求lock并被阻塞时,如果被interrupt,则“此线程会被唤醒并被要求处理InterruptedException”。
2⃣️区分几种锁:
-- 独占锁
-- 可重入锁ReetrantLock: 如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
-- 可中断锁
-- 公平锁
-- 读写锁
3⃣️CAS和乐观锁:乐观锁( Optimistic Locking
)其实是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
上面提到的乐观锁的概念中其实已经阐述了他的具体实现细节:主要就是两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是Compare and Swap(CAS
)。
Java内存模型(干货)
深入理解乐观锁和悲观锁在关系型数据库上的应用:不要把乐观并发控制和悲观并发控制狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。其实,在DBMS中,悲观锁正是利用数据库本身提供的锁机制来实现的。
4⃣️Java的AQS详解(ReentrantLock/Semaphore/CountDownLatch都是通过抽象队列式同步器AbstractQueuedSynchronizer实现的)待仔细研究
part10 voliate和synchronized区别
谈谈voliate(戳我)
同步
如用synchronized关键字,或者使用锁对象.
volatile
使用volatile关键字
用一句话概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道.
volatile详解
首先我们要先意识到有这样的现象,编译器为了加快程序运行的速度,对一些变量的写操作会先在寄存器或者是CPU缓存上进行,最后才写入内存.
而在这个过程,变量的新值对其他线程是不可见的.而volatile的作用就是使它修饰的变量的读写操作都必须在内存中进行!
volatile与synchronized
1⃣️volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
2⃣️volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
3⃣️volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性.
4⃣️volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
5⃣️volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化.
part11 ThreadLocal关键字
ThreadLocal的原理,每个Thread对象内部有个ThreadLocalMap,当线程访问ThreadLocal对象时,会在线程内部的ThreadLocalMap新建一个Entry,这样的话每个线程都有一个对象的副本,保证了并发场景下的线程安全。
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1.每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2.将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。
ThreadLocal在web应用开发中是一种很常见的技巧,当web端采用无状态写法时(比如stateless session bean和spring默认的singleton),就可以考虑把一些变量放在ThreadLocal中。
我理解ThreadLocal的使用场景是某些对象在多线程并发访问时可能出现问题,比如使用SimpleDataFormat的parse()方法,内部有一个Calendar对象,调用SimpleDataFormat的parse()方法会先调用Calendar.clear(),然后调用Calendar.add(),如果一个线程先调用了add()然后另一个线程又调用了clear(),这时候parse()方法解析的时间就不对了,我们就可以用ThreadLocal<SimpleDataFormat>来解决并发修改的问题。
另一种场景是Spring事务,事务是和线程绑定起来的,Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离。
public abstract class TransactionSynchronizationManager {
//线程绑定的资源,比如DataSourceTransactionManager绑定是的某个数据源的一个Connection,在整个事务执行过程中
//都使用同一个Jdbc Connection
}
part12 当HashMap中链表过长,会转换成二叉树提高检索效率。
hashcode怎么计算?=>把八位的十六进制的逻辑内存转换成十进制。
part13 Http协议中GET与POST的区别
a) GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连。POST把提交的数据则放置在是HTTP包的包体中。
b) GET传递的数据长度有限制,POST方式在理论上是没有大小限制的,可以传输大量数据。
c) POST的安全性要比GET的安全性高。因为GET方式传输的数据以明文的形式出现在URL中。
Http协议的状态:(200 – 服务器成功返回网页 404 – 请求的网页不存在 503 – 服务不可用 )
a) 2xx(成功),表示成功处理了请求。
b) 3xx(重定向),表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。
301 (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。 302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。305 (使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。
d) 4xx(请求错误),这些状态代码表示请求可能出错,妨碍了服务器的处理。
401 (未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。403 (禁止) 服务器拒绝请求。404 (未找到) 服务器找不到请求的网页。
e) 5xx(服务器错误),这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误。
500 (服务器内部错误) 服务器遇到错误,无法完成请求。 501 (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。 502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。503 (服务不可用) 服务器目前无法使用。
Part13阻塞队列和非阻塞队列:https://blog.csdn.net/madun/article/details/20313269
part14 线程池核心的概念
(1)handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
这四个都是静态内部类,有下面的方法。
void |
(2)阻塞队列BolckingQueue:
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列,此队列按照先进先出(FIFO)的原则对元素进行排序,不保证线程公平的访问队列;
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列,FIFO先进先出,Integer.MAX_VALUE。
- PriorityBlockingQueue:一个支持优先级排序的*阻塞队列。
- DelayQueue:是一个支持延时获取元素的*阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列。
- LinkedTransferQueue:一个由链表结构组成的*阻塞队列。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
part15 spring七个事务传播属性:
-
1.PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
-
2.PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
-
3.PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
-
4.PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
-
5.PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
-
6.PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
-
7.PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
备注:常用的两个事务传播属性是1和4,即PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW。
part16 MyBatis
MyBatis : SqlSessionFactoryBuilder -> SqlSessionFactory -> SqlSession
InputStream config = Resources.getResourcesAsStream("sqlMapconfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBUilder().build(config);
SqlSession session = factory.openSession();
Mybatis+Spring:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:org.sunny.*.xml"/>
</bean>
sql注入:SQL注入之所以发生,是因为我们碰到的是完全一样的问题:一个查询(一系列的指令)会有多个参数(数据)插入其中,而这些参数被当做指令执行从而导致异常。一个恶意的用户可以利用这样的漏洞来让数据库返回所有的用户的信息,很显然,这是不对的!
比如说,使用的是Statement,这相当于连接好字符串之后才进行sql编译,“select * from table where id”+condition;如果condition本来希望是=‘lily’,但是注入一个!=null,那么就会返回所有的信息。
解决办法:从前端来说,增加检查是否包含非法字符或者使用正则表达式过滤;后端,如果注入的是数据,尽量使用PreparedStatement,因为PreparedStatement可以预编译sql语句,不但执行速度快而且默认注入的是数据,防止sql注入,也可以使用正则表达式或者一些规则去过滤传进来的参数;myBatis中,#{}底层使用的是PreparedStatement,也就是说传进来的东西会默认当成数据处理,而${}底层使用的是Statement,比如定位一些表之类的参数就必须使用$。
PS:JDBC怎么连接
Class.forName("com.mysql.jdbc.Driver");//加载驱动
Connection conn = DriverManager.getConnection(url,user,password);
PreparedStatement ps = conn.prepareStatement(String sql);
或者Statement st = conn.createStatement();
part17:缓存
MyBatis的一级缓存是SqlSession级别的缓存,在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用于缓存数据,不同sqlSession之间的缓存数据区域是互不影响的。二级缓存指的是mapper级别的缓存,多个sqlSession使用同一个mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,同样使用HashMap缓存数据。
part18:JVM虚拟机的生命周期
--Bootstrap classloader
--Exetention classloader
--System classloader
--自定义的classloader
类的加载步骤:
(1)装载——查找并装载类型的二进制数据。
(2)连接——指向验证、准备、以及解析(可选)。
● 验证 确保被导入类型的正确性。
● 准备 为类变量分配内存,并将其初始化为默认值。
● 解析 把类型中的符号引用转换为直接引用。
(3)初始化——把类变量初始化为正确初始值。
类加载机制:全盘负责:当一个classLoader负责加载某个class文件时,该class文件所依赖的其他class文件也由该classLoader负责加载,除非显示指定其他的classLoader;
父类委托:先让父classLoader尝试加载该class,父加载器失败了,才尝试从自己的类加载路径加载该类;
缓存机制:保证所有加载过的class文件都会被缓存,当需要某个class时,先在缓存区搜索,没有再进行加载。
这里的父子类加载器指的是类加载器实例之间的关系。
part18+:运行数据区的Java栈和pc计数器是怎么配合工作的:
Java栈是由许多栈帧(frame)组成,一个栈帧包含一个Java方法的调用状态。当现成调用一个Java方法时,JVM压入一个新的栈帧到该线程的Java栈中;当方法返回时,这个栈帧被从Java栈中弹出并抛弃。
栈帧由三部分构成:局部变量区,操作数栈和帧数据区。局部变量区被组织为一个数组,该数组单个元素长度为一个JVM字(注意不是一个机器字),用于保存位于方法内的局部变量数据;操作数栈也是被组织为一个单位长度为一个JVM字的数组,不过该数组只能通过标准栈操作(push和pop)访问,该栈是被(逻辑上的)CPU的ALU唯一识别的操作数来源。关于局部变量和操作数栈的操作详见下段。帧数据区保存一些数据来支持常量池解析、正常方法返回以及异常派发机制。
如有分别存在局部变量区的a,b,c,要计算c=a+b,则使用4条指令:
- 读局部变量区[0]的值,压入操作数栈;
- 读局部变量区[1]的值,压入操作数栈;
- 弹出操作数栈栈顶值,再弹出操作数栈栈顶值,相加,把结果压入操作数栈;
- 弹出操作数栈栈顶值,写入局部变量区。
关于JVM内存的N个问题
part19:垃圾收集算法
(0)根搜索法
目前Java中可以作为GC ROOT的对象有:
1、虚拟机栈中引用的对象(本地变量表)
2、方法区中静态属性引用的对象
3、方法区中常亮引用的对象
4、本地方法栈中引用的对象(Native对象)
基本所有GC算法都引用根搜索算法这种概念。
(1)标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收所有标记的对象,标记过程在上面提过。
不足:1.效率问题,标记和清除两个过程效率不高
2.空间问题,标记清楚产生大量内存碎片
(2)复制算法:将内存安装容量分为大小相等的两块,每次使用其中一块。当回收时,将存活的对象复制到
为使用的内存中,清楚已使用过的内存。不足:内存缩小到运来的一半。
Java在新生代中采用这种算法,不过是将内存分为Eden和两块Survivor,每次使用Eden和其中的一块Suvivor,当回收时,将Eden和Suvivor存活的内存复制到未使用的Suvivor空间。HotSpot默认Eden与Suvivor比例为8:1,相当于可以使用90%的内存,如果存活的内存超过Suvivor空间,就是用老年代进行分配担保。
(3)标记-整理算法:过程与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象向一端移动,然后清理掉端边界以外的内存。
分代收集算法:将内存划分成几块,各个区域采用最适当的收集算法。Java一般把Java堆分为新生代和老年代,按各个年代特点采用适当的算法。
垃圾收集器:
新生代:Serial收集器,ParNew收集器,Parallel Scavenge收集器
老年代:CMS, Seral Old(MSC), Parallel Old收集器
GI收集器
新建对象优先放在Eden区分配内存,如果Eden区已满,在创建对象的时候,会因为无法申请到空间而触发minorGc操作,minorGc主要用来对年轻代垃圾进行回收,把eden中不能回收的对象放入到空的Survivor区,另一个Survivor区里不能被垃圾回收的对象也会被放入到这个Survivor区,这样能保证有一个Survivor区是空的。如果在这个过程中发现Survivor区的对象也满了,就会把这些对象复制到老年代,或者Survivor区没有满,但是有些对象已经存放非常长的时间,这些对象也会被放到老年代中,如果老年代也满了,就会出发fullGc。fullGc是用来清理整个堆空间的,包括年轻代和永久代。所以fullGc会造成很大的系统开销。Java8中已经移除了永久代,新加了一个称为元数据区的native的内存区,所以,大部分类的元数据都在本地内存中分配。
JVM GC什么时候执行?
eden区空间不够存放新对象的时候,执行Minro GC。升到老年代的对象大于老年代剩余空间的时候执行Full GC,或者小于的时候被HandlePromotionFailure 参数强制Full GC 。调优主要是减少 Full GC 的触发次数,可以通过 NewRatio 控制新生代转老年代的比例,通过MaxTenuringThreshold 设置对象进入老年代的年龄阀值(
part20: Java四种引用
强引用(是指创建一个对象并把这个对象赋给一个引用变量);
软引用(SoftReference):如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。 SoftReference的特点是它的一个实例保存对一个Java对象的软引用, 该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。另外,一旦垃圾线程回收该Java对象之 后,get()方法将返回null。
弱引用(WeakReference):弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。
虚引用(PhantomReference):仅用于在发生GC时接收一个系统通知。
part21:数据库的乐观锁和悲观锁?(数据库有两个线程在同时访问一行数据,一个线程写,一个线程读,可以读出来么?)
part22: MySQL语句的执行顺序
(7) SELECT
(8) DISTINCT <select_list>
(1) FROM <left_table>
(3) <join_type> JOIN <right_table>
(2) ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) HAVING <having_condition>
(9) ORDER BY <order_by_condition>
(10) LIMIT <limit_number>
part23:Spring中使用了哪些设计模式
1.工厂模式,这个很明显,在各种BeanFactory以及ApplicationContext创建中都用到了;
2.模版模式,这个也很明显,在各种BeanFactory以及ApplicationContext实现中也都用到了;
3.代理模式,在Aop实现中用到了JDK的动态代理;
4.单例模式,这个比如在创建bean的时候。
5.Tomcat中有很多场景都使用到了外观模式,因为Tomcat中有很多不同的组件,每个组件需要相互通信,但又不能将自己内部数据过多地暴露给其他组件。用外观模式隔离数据是个很好的方法。
6.策略模式在Java中的应用,这个太明显了,因为Comparator这个接口简直就是为策略模式而生的。Comparable和Comparator的区别一文中,详细讲了Comparator的使用。比方说Collections里面有一个sort方法,因为集合里面的元素有可能是复合对象,复合对象并不像基本数据类型,可以根据大小排序,复合对象怎么排序呢?基于这个问题考虑,Java要求如果定义的复合对象要有排序的功能,就自行实现Comparable接口或Comparator接口.
7.原型模式:使用原型模式创建对象比直接new一个对象在性能上好得多,因为Object类的clone()方法是一个native方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
8.迭代器模式:Iterable接口和Iterator接口 这两个都是迭代相关的接口,可以这么认为,实现了Iterable接口,则表示某个对象是可被迭代的;Iterator接口相当于是一个迭代器,实现了Iterator接口,等于具体定义了这个可被迭代的对象时如何进行迭代的。
part22+:Spring中配置为成员变量注入属性:
@Bean(name="") //修饰一个方法,将方法的返回值注入到该bean中;
@Resource(name="")//可以修饰setter方法或者实力变量,将名称为xx的bean注入到setter方法或者成员变量中;
@Value("abc") //用于修饰一个Field,用于为该Field配置一个值,相当于配置一个变量;
@AutoWired //可以修饰构造器和成员变量,依据类型自动注入
part24: HashMap的key可以是可变的对象吗???
只要MutableKey 对象的成员变量改变,那么该对象的哈希值也改变了,所以该对象是一个可变的对象。
HashMap 的每个 bucket 里只有一个 Entry时,HashMap 可以根据索引、快速地取出该 bucket 里的 Entry;在发生“Hash 冲突”的情况下,单个 bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那系统必须循环到最后才能找到该元素。
同时我们也看到,判断是否找到该对象,我们还需要判断他的哈希值是否相同,假如哈希值不相同,根本就找不到我们要找的值。如果Key对象是可变的,那么Key的哈希值就可能改变。在HashMap中可变对象作为Key会造成数据丢失。
在HashMap中使用不可变对象。在HashMap中,使用String、Integer等不可变类型用作Key是非常明智的。我们也能定义属于自己的不可变类。如果可变对象在HashMap中被用作键,那就要小心在改变对象状态的时候,不要改变它的哈希值了。我们只需要保证成员变量的改变能保证该对象的哈希值不变即可。
part25:复制implements Cloneable
import java.io.Serializable;
public class BinaryNode<T> implements Serializable,Cloneable{
private T data;
private BinaryNode<T> rightChild;
private BinaryNode<T> leftChild;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public BinaryNode<T> getRightChild() {
return rightChild;
}
public void setRightChild(BinaryNode<T> rightChild) {
this.rightChild = rightChild;
}
public BinaryNode<T> getLeftChild() {
return leftChild;
}
public void setLeftChild(BinaryNode<T> leftChild) {
this.leftChild = leftChild;
}
@Override
protected BinaryNode<T> clone() throws CloneNotSupportedException {
BinaryNode<T> newNode = (BinaryNode<T>) super.clone();
newNode.leftChild = newNode.leftChild.clone();
newNode.rightChild = newNode.rightChild.clone();
return newNode;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BinaryNode<?> that = (BinaryNode<?>) o;
if (data != null ? !data.equals(that.data) : that.data != null) return false;
if (rightChild != null ? !rightChild.equals(that.rightChild) : that.rightChild != null) return false;
return leftChild != null ? leftChild.equals(that.leftChild) : that.leftChild == null;
}
@Override
public int hashCode() {
int result = data != null ? data.hashCode() : 0;
result = 31 * result + (rightChild != null ? rightChild.hashCode() : 0);
result = 31 * result + (leftChild != null ? leftChild.hashCode() : 0);
return result;
}
}
part26:TCP和UDP的区别
TCP和UDP都是传输层协议,其中TCP是基于连接的协议,它的传输模式是流模式传输,而UDP是无连接的传输协议,它是以数据报的模式进行传输;TCP可以保证传输的正确性,并且他有拥塞控制(缓冲区,窗口协议)、流量控制(网络阻塞)、超时重传、丢弃重复传输的数据等机制,而UDP可能丢包而且不保证数据传输的正确性。TCP的首部开销为20个字节,包括***seq、检验和、确认应答ACK等,UDP首部开销为8个字节,只提供了检验和。
part27: try-with-resources以及底层机制
try-with-resource并不是JVM虚拟机的新增功能,只是JDK实现了一个语法糖,当你将上面代码反编译后会发现,其实对JVM虚拟机而言,它看到的依然是之前的写法。实现了java中的java.lang.AutoCloseable接口。所有实现了这个接口的类都可以在try-with-resources结构中使用。
part28:数据库的隔离级别:Read Uncommitted, Read commited, Repeatable read, Serializable
Read Uncommitted. 最低的隔离级别,Read Uncommitted最直接的效果就是一个事务可以读取另一个事务并未提交的更新结果。
Read Committed. Read Committed通常是大部分数据库采用的默认隔离级别,它在Read Uncommitted隔离级别基础上所做的限定更进一步, 在该隔离级别下,一个事务的更新操作结果只有在该事务提交之后,另一个事务才可能读取到同一笔数据更新后的结果。 所以,Read Committed可以避免Read Uncommitted隔离级别下存在的脏读问题, 但,无法避免不可重复读取和幻读的问题。
Repeatable Read. Repeatable Read隔离级别可以保证在整个事务的过程中,对同一笔数据的读取结果是相同的,不管其他事务是否同时在对同一笔数据进行更新,也不管其他事务对同一笔数据的更新提交与否。 Repeatable Read隔离级别避免了脏读和不可重复读取的问题,但无法避免幻读。(mysql默认隔离级别)
Serializable. 最为严格的隔离级别,所有的事务操作都必须依次顺序执行,可以避免其他隔离级别遇到的所有问题,是最为安全的隔离级别, 但同时也是性能最差的隔离级别,因为所有的事务在该隔离级别下都需要依次顺序执行,所以,并发度下降,吞吐量上不去,性能自然就下来了。 因为该隔离级别极大的影响系统性能,所以,很少场景会使用它。通常情况下,我们会使用其他隔离级别加上相应的并发锁的机制来控制对数据的访问,这样既保证了系统性能不会损失太大,也能够一定程度上保证数据的一致性。
MySQL MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是,把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能.
MVCC可以提供基于某个时间点的快照,使得对于事务看来,总是可以提供与事务开始时刻相一致的数据,而不管这个事务执行的时间有多长.所以在不同的事务看来,同一时刻看到的相同行的数据可能是不一样的,即一个行可能有多个版本.是否听起来不可思议呢?
原来,为了实现mvcc, innodb对每一行都加上了两个隐含的列,其中一列存储行被更新的”时间”,另外一列存储行被删除的”时间”. 但是innodb存储的并不是绝对的时间,而是与时间对应的数据库系统的版本号,每当一个事务开始的时候,innodb都会给这个事务分配一个递增的版本号,所以版本号也可以被认为是事务号.对于每一个”查询”语句,innodb都会把这个查询语句的版本号同这个查询语句遇到的行的版本号进行对比,然后结合不同的事务隔离等级,来决定是否返回该行.
上述策略的优点是,在读取数据的时候,innodb几乎不用获得任何锁, 每个查询都通过版本检查,只获得自己需要的数据版本,从而大大提高了系统的并发度.这种策略的缺点是,为了实现多版本,innodb必须对每行增加相应的字段来存储版本信息,同时需要维护每一行的版本信息,而且在检索行的时候,需要进行版本的比较,因而降低了查询的效率;innodb还必须定期清理不再需要的行版本,及时回收空间,这也增加了一些开销。
并发问题
脏读
(针对未提交数据)如果一个事务中对数据进行了更新,但事务还没有提交,另一个事务可以“看到”该事务没有提交的更新结果,这样造成的问题就是,如果第一个事务回滚,那么,第二个事务在此之前所“看到”的数据就是一笔脏数据。
不可重复读
(针对其他提交前后,读取数据本身的对比)不可重复读取是指同一个事务在整个事务过程中对同一笔数据进行读取,每次读取结果都不同。如果事务1在事务2的更新操作之前读取一次数据,在事务2的更新操作之后再读取同一笔数据一次,两次结果是不同的,所以,Read Uncommitted也无法避免不可重复读取的问题。
幻读
(针对其他提交前后,读取数据条数的对比) 幻读是指同样一笔查询在整个事务过程中多次执行后,查询所得的结果集是不一样的。幻读针对的是多笔记录。在Read Uncommitted隔离级别下, 不管事务2的插入操作是否提交,事务1在插入操作之前和之后执行相同的查询,取得的结果集是不同的,所以,Read Uncommitted同样无法避免幻读的问题。
不可重复读和幻读区别
对于不可重复读和幻读,可以借用下面的例子理解:
不可重复读的重点是修改:同样的条件, 你读取过的数据, 再次读取出来发现值不一样了;
幻读:幻读的重点在于新增或者删除 (数据条数变化),同样的条件, 第1次和第2次读出来的记录数不一样
Innodb强调多功能性,支持的拓展功能比较多,myisam主要侧重于性能。
- InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
- InnoDB是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
- InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
- Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高;
如何选择
- 是否要支持事务,如果要请选择innodb,如果不需要可以考虑MyISAM;
- 如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读写也挺频繁,请使用InnoDB。
- 系统奔溃后,MyISAM恢复起来更困难,能否接受;
- MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不会差。
全文索引
使用索引是数据库性能优化的必备技能之一。在MySQL数据库中,有四种索引:聚集索引(主键索引)、普通索引、唯一索引以及我们这里将要介绍的全文索引(FULLTEXT INDEX)。
全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用「分词技术「等多种算法智能分析出文本文字中关键字词的频率及重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。在这里,我们就不追根究底其底层实现原理了,现在我们来看看在MySQL中如何创建并使用全文索引。
我们想要在article
表的title
和content
列中全文检索指定的查询字符串,可以如下编写SQL语句:
SELECT * FROM article WHERE MATCH(title, content) AGAINST('查询字符串')
强烈注意:MySQL自带的全文索引只能用于数据库引擎为MyISAM的数据表,如果是其他数据引擎,则全文索引不会生效。此外,MySQL自带的全文索引只能对英文进行全文检索,目前无法对中文进行全文检索。如果需要对包含中文在内的文本数据进行全文检索,我们需要采用Sphinx(斯芬克斯)/Coreseek技术来处理中文。本站将会在后续文章中对Sphinx以及Coreseek进行介绍。
.BeanFactory 接口和 ApplicationContext 接口有什么区别 ?
①ApplicationContext 接口继承BeanFactory接口,Spring核心工厂是BeanFactory ,BeanFactory采取延迟加载,第一次getBean时才会初始化Bean, ApplicationContext是会在加载配置文件时初始化Bean。
②ApplicationContext是对BeanFactory扩展,它可以进行国际化处理、事件传递和bean自动装配以及各种不同应用层的Context实现
开发中基本都在使用ApplicationContext, web项目使用WebApplicationContext ,很少用到BeanFactory
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
IHelloService helloService = (IHelloService) beanFactory.getBean("helloService");
helloService.sayHello();
Bean的完整生命周期 (十一步骤)
①instantiate bean对象实例化
②populate properties 封装属性
③如果Bean实现BeanNameAware 执行 setBeanName
④如果Bean实现BeanFactoryAware 或者 ApplicationContextAware 设置工厂 setBeanFactory 或者上下文对象 setApplicationContext
⑤如果存在类实现 BeanPostProcessor(后处理Bean) ,执行postProcessBeforeInitialization,BeanPostProcessor接口提供钩子函数,用来动态扩展修改Bean。(程序自动调用后处理Bean)
publicclassMyBeanPostProcessorimplementsBeanPostProcessor{
publicObject postProcessAfterInitialization(Object bean,String beanName)
throwsBeansException{
System.out.println("第八步:后处理Bean,after初始化。");
//后处理Bean,在这里加上一个动态代理,就把这个Bean给修改了。
return bean;//返回bean,表示没有修改,如果使用动态代理,返回代理对象,那么就修改了。
}
publicObject postProcessBeforeInitialization(Object bean,String beanName)
throwsBeansException{
System.out.println("第五步:后处理Bean的:before初始化!!");
//后处理Bean,在这里加上一个动态代理,就把这个Bean给修改了。
return bean;//返回bean本身,表示没有修改。
}
}
注意:这个前处理Bean和后处理Bean会对所有的Bean进行拦截。
⑥如果Bean实现InitializingBean 执行 afterPropertiesSet
⑦调用<bean init-method="init"> 指定初始化方法 init
⑧如果存在类实现 BeanPostProcessor(处理Bean) ,执行postProcessAfterInitialization
⑨执行业务处理
⑩如果Bean实现 DisposableBean 执行 destroy
⑪调用<bean destroy-method="customerDestroy"> 指定销毁方法 customerDestroy
5.请介绍一下Spring框架中Bean的生命周期和作用域
##作用域
singleton
当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
prototype
Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean() 方法)时都会创建一个新的bean实例。根据经验,对所有有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用 singleton作用域
request
在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用 域仅在基于web的Spring ApplicationContext情形下有效。
session
在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
global session
在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于 web的Spring ApplicationContext情形下有效。
哪些是最重要的bean生命周期方法?能重写它们吗?
有两个重要的bean生命周期方法。第一个是setup方法,该方法在容器加载bean的时候被调用。第二个是teardown方法,该方法在bean从容器中移除的时候调用。
bean标签有两个重要的属性(init-method 和 destroy-method),你可以通过这两个属性定义自己的初始化方法和析构方法。Spring也有相应的注解:@PostConstruct 和 @PreDestroy。
Spring如何处理线程并发问题?
Spring使用ThreadLocal解决线程安全问题
我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程*享了。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
算法逻辑题:判断链表是否有环,环的入口,以及两个链表的交点位置。
数据库查询(学生查询):https://blog.csdn.net/mayanyun2013/article/details/50845667
进程间通信:https://www.cnblogs.com/xh0102/p/5710074.html
# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量(semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 (sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
# 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
数据库的乐观锁:乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。
总结
-
乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能
-
乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方
(1)CAS的含义: CAS是compare and swap的缩写,即我们所说的比较交换。cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。 CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址V的值和预期值A的值相等,将内存地址V中的值更新成B,否则,CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
(2)CAS的问题 ①.CAS容易造成ABA问题。一个线程a将数值改成了b,接着又改成了a,此时CAS认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version加1。在java5中,已经提供了AtomicStampedReference来解决问题。 ②.CAS造成CPU利用率增加。之前说过了CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。
TCP释放连接时为什么time_wait状态必须等待2MSL时间
答: 2MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态,当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间,等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。还有一个原因是避免和下一次连接的混淆。
IP是网络层协议,IP协议保证了计算机间可以发送和接受数据,但其不能解决数据分组在传输过程的问题。
TCP/UDP是传输层协议。