面试自用--Spring

Spring

标题面向对象 和 面向过程 的区别

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

其实就是两句话,面向对象就是高度实物抽象化、面向过程就是自顶向下的编程

标题重载和重写

override(重写)overload(重载)
方法名、参数、返回值相同。参数类型、个数、顺序至少有一个不相同。
子类方法不能缩小父类方法的访问权限不能重载只有返回值不同的方法名。
子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
存在于父类和子类之间存在于父类和子类、同类中。
方法被定义为final不能被重写

标题this的用法

  1. 普通的直接引用,this相当于是指向当前对象本身
  2. 形参与成员名字重名,用this来区分
  3. 遵循就近原则(当参数没有使用this时,谁离得近,参数指代谁)
public Person{
	private String name;  /*1*/
	private int age;
	
	public Person(String name/*2*/, int age){
	    this.name/*3*/ = name/*4*/; 
	    //name4没有使用this,根据就近原则,name2离name4更近,所以name4指代name2;name3使用this,所以name3指向对象本身即name1。this指的是Person即Person.name = (String)name
	    this.age = age;
	}
}
  1. 引用本类的构造函数
class Person {
    private String name;
    private int age;

    public Person() {
    }
    public Person(String name) {
        this.name = name;
    }
    public Person(String name, int age) {
        this(name);  //this(name) 等价于 Person(name)
        this.age = age;
    }
}

设计模式的七大原则(这里的修改都多是指需改代码)

  • 单一职责原则类的功能要单一,不能包罗万象,跟杂货铺似的。 一个类只做一个事儿。
  • 依赖倒转原则面向接口编程,一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。 依赖倒转原则
  • 里式替换原则不破坏继承关系,子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。里式替换原则
  • 开闭原则:编程中最基础、最重要设计原则 ,一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方),当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化 ,例如:
 /**
     * Ocp1
     * 优点是比较好理解,简单易操作
     * 缺点是违反了设计模式的ocp原则,即对扩展开放,对修改关闭
     * 比如我们这时要新增加一个图形种类,我们需要修改的地方比较多,如Ocp2
     * 使用开闭原则进行修改,如Ocp3
     */
     public class Ocp1 {
         public static void main(String[] args) {
             GraphicEditor graphicEditor  = new GraphicEditor();
             graphicEditor.drawShape(new Rectangle());
             graphicEditor.drawShape(new Circle());
         }
     }
     //使用方
     class GraphicEditor{
         public void drawShape(Shape s){
             if(s.m_type == 1){
                 drawRectangle(s);
             }else if(s.m_type == 2){
                 drawCircle(s);
             }
         }
         private void drawRectangle(Shape r){
             System.out.println("绘制矩形");
         }
         private void drawCircle(Shape r){
             System.out.println("绘制圆形");
         }
     }
     //提供方 
     class Shape{
         int m_type;
     }
     //子类
     class Rectangle extends Shape{
         Rectangle(){
             super.m_type = 1;
         }
     }
     class Circle extends Shape{
         Circle(){
             super.m_type = 2;
         }
     }
     
     /******************************这是一条分割线****************************************/
     public class Ocp2 {
         public static void main(String[] args) {
             GraphicEditor graphicEditor  = new GraphicEditor();
             graphicEditor.drawShape(new Rectangle());
             graphicEditor.drawShape(new Circle());
             graphicEditor.drawShape(new Triangle());
         }
     }
     //使用方修改判断新增s.m_type == 3
     class GraphicEditor{
         public void drawShape(Shape s){
             if(s.m_type == 1){
                 drawRectangle(s);
             }else if(s.m_type == 2){
                 drawCircle(s);
             }else if(s.m_type == 3){
                 drawTriangle(s);
             }
         }
         private void drawRectangle(Shape r){
             System.out.println("绘制矩形");
         }
         private void drawCircle(Shape r){
             System.out.println("绘制圆形");
         }
         private void drawTriangle(Shape r){
             System.out.println("绘制三角形");
         }
     }
      //提供方
     class Shape{
         int m_type;
     }
     //子类 
     class Rectangle extends Shape{
         Rectangle(){
             super.m_type = 1;
         }
     }
     class Circle extends Shape{
         Circle(){
             super.m_type = 2;
         }
     }
     class Triangle extends Shape{
         Triangle(){
             super.m_type = 3;
         }
     }
     /******************************这是一条分割线****************************************/
     public class Ocp3 {
         public static void main(String[] args) {
             GraphicEditor graphicEditor  = new GraphicEditor();
             graphicEditor.drawShape(new Rectangle());
             graphicEditor.drawShape(new Circle());
             graphicEditor.drawShape(new Triangle());
         }
     }
      //使用方
     class GraphicEditor{
         public void drawShape(Shape s){
             s.draw();//直接调用s.draw()就完事了
         }
      
     }
     //提供方 
     abstract class Shape{
         int m_type;
         public abstract void draw();//抽象方法
     }
     //子类 
     class Rectangle extends Shape{
         Rectangle(){
             super.m_type = 1;
         }
         @Override
         public void draw() {
             System.out.println("绘制矩形");
         }
     }
     class Circle extends Shape{
         Circle(){
             super.m_type = 2;
         }
         @Override
         public void draw() {
             System.out.println("绘制圆形");
         }
     }
     class Triangle extends Shape{
         Triangle(){
             super.m_type = 3;
         }
         @Override
         public void draw() {
             System.out.println("绘制三角形");
         }
     }

普通类,抽象类和接口

抽象和具体的概念

抽象类和具体类是相对的概念。“抽象”是一种存在思想逻辑中的概念,而“具体”是一种可见可触摸的现实对象。简单说,比如“人”比“男人”抽象一点,“动物”又比“人”更抽象一点,而“生物”又比“动物”更抽象。

抽象的概念是由具体概念依其“共性”而产生的,把具体概念的诸多个性排出,集中描述其共性,就会产生一个抽象性的概念。抽象思维,是人类思维达到高级阶段产生的一种能力,例如,当小孩子思维尚未成熟时,他们只能掌握具体概念,他们在学习代词“你、我、他”时往往遇到困难,因为代词具有较高的抽象性。

总之,抽象概念的外延大,内涵小;具体概念的外延小,内涵大

抽象类,提取共性时多使用;接口,扩展操作时多使用

抽象类与普通类

  1. 普通类可以去实例化调用;抽象类不能被实例化,因为它是存在于一种概念而不非具体。
  2. 普通类和抽象类都可以被继承,但是抽象类被继承后子类必须重写继承的方法,除非自类也是抽象类。
    实例演示:
 //这是宠物类,普通父类,方法里是空的
   public class Pet {
       public void play(){}               
   }
   //这是子类,是一个猫类,重写了父类方法
   public class Cat extends Pet {    
       public void play(){
           System.out.println("1、猫爬树");
       }
   }
   //这是子类,是一个狗类,重写了父类方法
   public class Dog extends Pet {    
       public void play(){
           System.out.println("2、狗啃骨头");
       }
   }
   //这是测试类,分别调用了子类的不同方法
   public class Test {
       public static void main(String[] args) {  
   		//多典型的多态表现啊,相当的给力
           Pet p1=new Cat();                               
           Pet p2=new Dog();
           p1.play();
           p2.play();
       }
   }
   /**
   * 输出结果:
   * 1、猫爬树
   * 2、狗啃骨头
   /

问:

    把父类改成抽象类,方法改成抽象方法,那么public void play();  子类不变,依然重写父类方法,那这个跟普通父类没区别啊?难道说就一个抽象方法没方法体就完事了??那我普通方法有方法体,我空着不写内容不就得了,不跟抽象方法一个样吗??别跟我说抽象类还不能实例化,哥也不需要去new它!普通类都能搞定的,还弄个抽象类有什么意义?我前面都说了普通类的方法我可以空着不写,达到跟抽象类方法没方法体一样的效果。既然两种方式都能达到同一个输出效果,弄一种方式不就得了,那为什么还要创造出一个抽象类出来?难道是比普通类看着舒服?用着爽?还是更加便捷?还是为了强制让别人用的时候必须强制化实现抽象方法省的你忘了什么的?

答:

    就是为了强制不能实例化,以及强制子类必须实现方法这不是你忘不忘的问题,你说你不去new它就行了,这话没错.
    那你想另一个问题,为什么要有访问控制呢?为什么要有private和public之分呢?我可以全部public,不该访问的,我不访问就行了啊?小程序里,看不出什么区别,反而private成员要写一堆set和get函数,多麻烦,我自己写小程序的时候也会偷懒全部public,但是项目大了,代码多了,这种严谨的结构就很重要了。且不说会有很多人合作一起写一个程序,哪怕还是你一个人写,也保不住有忘记的时候,那时候编译器不报错,茫茫码海上哪找错误去面向对象说到底就是方便你思考,易扩展、易维护管理,硬要说没必要,整个面向对象都没必要了,C语言有什么干不了的呀,运行效率还高。

抽象类与接口

    接口是对动作的抽象,这个对象能做什么。
    抽象类是对本质的抽象,这个对象是什么。
    
    比如,男人和女人,他们的抽象类是人类,而猫和狗的抽象类是宠物类。人类可以吃东西,宠物类也可以吃东西,但是两者不能混为一谈,因为有本质的区别。这个“吃东西”是一个动作,你可以把“吃东西”定义成一个接口,然后让两个类去实现它的方法。

    所以,在高级语言上,一个类只能继承一个类或抽象类,正如人不可能同时是动物类又是植物类,但是可以实现多个接口,例如,吃饭接口、呼吸接口等。
  • 使用情况
    • 抽象类 和 接口 都是用来抽象具体对象的,但是接口的抽象级别最高;
    • 抽象类可以有具体的方法和属性, 接口只能有抽象方法和不可变常量(final);
    • 抽象类主要用来抽象类别,接口主要用来抽象功能;
    • 抽象类中不包含任何实现,派生类必须覆盖它们。接口中所有方法都必须是未实现的;
    • 抽象类实现接口时,接口的方法在抽象类中可以被实现也可以不被实现,而普通实现接口必须实现所有接口方法。
  • 使用方向
    • 当你关注一个事物的本质的时候,用抽象类;
    • 当你关注一个操作的时候,用接口。

小结

    抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度的。

静态变量和实例变量的区别

  • 静态变量(static): 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。 变量的初始化顺序按照定义的顺序进行初始化
  • 实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。

内部类

  • 静态内部类
//静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量
public class Outer {
    private static int radius = 1;

    static class StaticInner {
        public void visit() {
            System.out.println("visit outer static variable:" + radius);
        }
    }
}
//创建方式
Outer.StaticInner inner = new Outer.StaticInner(); 
inner.visit();
  • 成员内部类
 //成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例
    public class Outer {
        private static int radius = 1;
        private int count = 2;
    
        class Inner {
            public void visit() {
                System.out.println("visit outer static variable:" + radius);
                System.out.println("visit outer variable:" + count);
            }
        }
    }
    //创建方式
    Outer outer = new Outer(); 
    Outer.Inner inner = outer.new Inner(); 
    inner.visit();
  • 局部内部类
 //定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。
    public class Outer {
        private int out_a = 1;
        private static int STATIC_b = 2;
    
        public void testFunctionClass() {
            int inner_c = 3;
            class Inner {
                private void fun() {
                    System.out.println(out_a);
                    System.out.println(STATIC_b);
                    System.out.println(inner_c);
                }
            }
            Inner inner = new Inner();
            inner.fun();
        }
    
        public static void testStaticFunctionClass() {
            int d = 3;
            class Inner {
                private void fun() {
                    // System.out.println(out_a); 编译错误,定义在静态方法中的局部 类不可以访问外部类的实例变量 
                    System.out.println(STATIC_b);
                    System.out.println(d);
                }
            }
            Inner inner = new Inner();
            inner.fun();
        }
    }
    //创建方式
    public class Outer {
        public static void testStaticFunctionClass() {
            class Inner {
            }
            Inner inner = new Inner();
        }
    }
  • 匿名内部类
  /**
    * 匿名内部类必须继承一个抽象类或者实现一个接口。
    * 匿名内部类不能定义任何静态成员和静态方法。
    * 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
    * 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
    */
    public class Outer {
        private void test(final int i) {
            new Service() {
                public void method() {
                    for (int j = 0; j < i; j++) {
                        System.out.println("匿名内部类");
                    }
                }
            }.method();
        }
    }
    
    //匿名内部类必须继承或实现一个已有的接口 
    interface Service {
        void method();
    }

局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?

是因为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。

BIO、NIO、AIO 有什么区别?

  • 同步阻塞BIO,一个连接一个线程。串行执行
  • 同步非阻塞NIO,NIO主要是想解决BIO的大并发问题,NIO是有buffer + channel + selector构成,通过多路选择器 + channel通道实现多进程并发处理,通道注册到多路选择器上,多路选择器通过轮训的方式来确定连接分发线程。
  • 异步非阻塞AIO,NIO升级,实现异步,多了4个异步通道类,AsynchronousSocketChannel,AsynchronousServerSocketChannel,AsynchronousFileChannel,AsynchronousDatagramChannel。

反射

Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。

反射3种方式

  • 利用对象获取类(意义不大,已经new了对象可以直接调用了)
   public static void main(String[] args) {
        Person person = new Person();
        Class<?> c = person.getClass();
        System.out.println(c.getName());
    }
  • 利用类名获取类(依赖太强,不符合解耦思想)
public static void main(String[] args) {
    Class<?> c =   Person.class;
    System.out.println(c.getName());
}
  • 利用类的全路径名获取类
 package com.xnrd.common.base;
    
    public class Person {
        public String height;
        public Double weight;  
        private int  age;
        
        public Person() {
            super();
        }
     
        public Person(int age) {
            super();
            this.age = age;
        }
     
        public Person(Double weight, int age) {
            super();
            this.weight = weight;
            this.age = age;
        }
     
        public Person(String height, int age) {
            super();
            this.height = height;
            this.age = age;
        }
     
        public Person(String height, Double weight, int age) {
            super();
            this.height = height;
            this.weight = weight;
            this.age = age;
        }
     
        public String getHeight() {
            return height;
        }
        public void setHeight(String height) {
            this.height = height;
        }
     
        public Double getWeight() {
            return weight;
        }
        public void setWeight(Double weight) {
            this.weight = weight;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        //自定义几个方法
        public void methodOne(String str) {
            System.out.println("您吃饭了吗?");
        }
        private void methodTwo() {
            System.out.println("我吃了?");
        }
        String methodThree(String height, Double weight,int age) {
            System.out.println("我是第三个方法!");
            return height+ weight+ age;
        }
        protected void methodFour() {
            System.out.println("我是第四个方法!");
        }
        public static void main(String[] args) {
            System.out.println("Person的main方法执行成功!");
        }
    }

反射调用

public static void getConstructors() {
    try {
        //全路径获取类
        Class<?> class1 = Class.forName("com.xnrd.common.base.Person");
        //获取类名
        System.out.println(class1.getName());
        //获取所有公用的构造方法
        Constructor<?>[] constructors = class1.getConstructors();
        for(Constructor<?> c : constructors) { 
            System.out.println(c);
        }
        //获取所有的构造方法(包括私有的)
        Constructor<?>[] declaredconstructors = class1.getDeclaredConstructors();
        for(Constructor<?> c : declaredconstructors) { 
            System.out.println(c);
        }
        //获取公有的有参构造方法
        Constructor<?> declaredcons1 = class1.getConstructor(new Class[] {Double.class,int.class});
        System.out.println(declaredcons1);
        //获取私有的有参构造
        Constructor<?> declaredconstructor1 = class1.getDeclaredConstructor(new Class[] {String.class,int.class});
        System.out.println(declaredconstructor1);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**************************这是一条分割线****************************/
public static void  getFilds() {
    try {
        Class<?> class1 = Class.forName("com.xnrd.common.base.Person");
        //获取所有公有字段
        Field[] fildFields = class1.getFields();
        for(Field field : fildFields) {
            System.out.println(field);
        }
        //获取所有字段(包括私有)
        Field[] fields = class1.getDeclaredFields();
        for(Field field : fields) {
            System.out.println(field);
        }
        //获取指定的字段
        Field field = class1.getField("height");
        //获取一个公有的无参构造,然后实例化,并给字段赋值
        Object object = class1.getConstructor().newInstance();
        field.set(object, "166");
        //测试是否设置成功
        Person person  = (Person)object;
        System.out.println(person.getHeight());
        Field field2 = class1.getDeclaredField("age");
        field2.setAccessible(true);     //暴力反射  获取私有字段必须添加此行
        field2.set(object, 25);
        System.out.println(person.getAge());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**************************这是一条分割线****************************/
public static void  getMethod() {
    try {
        Class<?> class1 = Class.forName("com.xnrd.common.base.Person");
        //获取所有public 修饰的方法
        Method[] methods = class1.getMethods();
        for(Method method : methods) {
            System.out.println(method);
        }
        //获取所有的方法(包括私有的)
        Method[] methods2 = class1.getDeclaredMethods();
        for(Method method : methods2) {
            System.out.println(method);
        }
        //获取类中的指定方法并使用
        Method method = class1.getMethod("methodOne", String.class);
        System.out.println(method);
        //获取类中指定的不带参数的方法并使用
        Method method2 = class1.getDeclaredMethod("methodTwo");
        System.out.println(method2);
        //若methodThree方法前修饰符是public 则用getMethod
        Method method3 = class1.getDeclaredMethod("methodThree", String.class,Double.class,int.class);
        System.out.println(method3);
        //实例化对象,验证方法是否获取成功
        Object object = class1.getConstructor().newInstance();
        //调用方法
        Object invObject =  method3.invoke(object, "166",3.14,25);
        System.out.println(invObject);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

静态代理

简单讲就是定义一个接口实现,需要什么功能就在里边加什么功能代码。实现简单但是会造成耦合性高,不符合设计思想。

动态代理(JDK,CGLIB->三方提供,须引入jar包):

当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。

动态代理的应用:

Spring的AOP

加事务

加权限

加日志

怎么实现动态代理?

核心思想是拦截;

首先必须定义一个接口,还要有一个InvocationHandler(将实现接口的类的对象传递给它)处理类。再有一个工具类Proxy(习惯性将其称为代理类,因为调用他的newInstance()可以产生代理对象,其实他只是一个产生代理对象的工具类)。利用到InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回

String

String str="i"与 String str=new String(“i”)一样吗?

String str="i"会将起分配到常量池中,常量池中没有重复的元素,如果常量池中存中i,就将i的地址赋给变量,如果没有就创建一个再赋给变量。

String str=new String(“i”)会将对象分配到堆中,即使内存一样,还是会重新创建一个新的对象。

String str=new String(“ab”)创建了几个对象

一个或两个

一个:常量池里有ab,这是只用在堆里new一个对象并指向常量池里的ab地址

两个:常量池里没有ab,在堆里new一个对象,并在常量池里创建一个ab的对象

String(“a”)+String(“b”)

创建了6个对象

对象1:StringBuilder(String是Final修饰不能进行相加操作,需转换为StringBuilder)

对象2:常量池创建a

对象3:new String(“a”)

对象4:常量池创建b

对象5:nwe String(“b”)

对象6:new String(“ab”)

String c=String(“a”)+String(“b”)

交上边多在常量池里创建了一个ab

StringBuffer,HashTable(synchronized)、ConcurrentHashMap(默认并发数16)是线程安全的;StringBuilder,HashMap是非线程安全的

Java容器

在这里插入图片描述

在 Queue 中 poll()和 remove()有什么区别?

(1)offer()和add()区别:

增加新项时,如果队列满了,add会抛出异常,offer返回false。

(2)poll()和remove()区别:

poll()和remove()都是从队列中删除第一个元素,remove抛出异常,poll返回null。

(3)peek()和element()区别:

peek()和element()用于查询队列头部元素,为空时element抛出异常,peek返回null。

线程的创建方式

继承Thread类创建线程

public class MyThread extends Thread{//继承Thread类
   //重写run方法
     public void run(){
       
   }
}
public class Main {
   public static void main(String[] args){
        //通过start创建并启动线程
     new MyThread().start();
   }
}

实现Runnable接口创建线程

public class MyThread2 implements Runnable {//实现Runnable接口
	//重写run方法
    public void run(){
        
    }
}
public class Main {
    public static void main(String[] args){
        //创建并启动线程
        MyThread2 myThread=new MyThread2();
        Thread thread=new Thread(myThread);
        thread().start();
        //或者    
        //new Thread(new MyThread2()).start();
    }
}

使用Callable和Future创建线程

public static class MyThread3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 15 + 16;
    }
}
public class Main {
    public static void main(String[] args) {
        MyThread3 th = new MyThread3();
        //使用Lambda表达式创建Callable对象, FutureTask类来包装Callable对象
        /*FutureTask<Integer> future = new FutureTask<>(
             () -> 5 + 6
        );*/
        FutureTask<Integer> future = new FutureTask<>(new MyThread3());
        //实质上还是以Callable对象来创建并启动线程
        new Thread(future, "有返回值的线程").start();
        try {
            //get()方法会阻塞,直到子线程执行结束才返回
            System.out.println("子线程的返回值:" + future.get());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

使用线程池创建管理线程

/**
 *	1. maximunPoolSize设置大小依据:
 *		有业务类型类配置,分为以下两种类型,可使用Runtime.getRuntime().availableProcessors()来获取服务器可使用的cpu核数,再根据以下
 *的两种类型来判断。
 *      - CPU密集型
 *        	该任务需要大量的运算,而且没有阻塞,需要CPU一直全速运行,
 *        	CPU密集任务只有在真正的多核CPU上才可能得到加速。
 *        	一般计算公式:CPU核数 + 1个线程的线程池
 *      - IO密集型
 *        	即该任务需要大量的IO(大量的阻塞),这种类型分以下两种情况设置
 *        	1,如果IO密集型任务线程并非一直在执行任务,则应配置尽可能多的线程,如CPU核数 * 2
 *        	2,参考公式:CPU核数 /1 - 阻塞系数                  阻塞系数在0.8~0.9之间
 *          	比如:8核CPU:8/1 - 0.9 = 80个线程数
 * 
 * 	2. 阻塞队列种类:
 *        - ArrayBlockingQueue 由数组结构组成的有界阻塞队列。初始化时传入大小,有界,FIFO,使用一个重入锁,默认使用非公平锁,入队和
 *出队共用一个锁,互斥。
 *        - LinkedBlockingQueue 由链表结构组成的有界阻塞队列。默认初始化大小为Integer.MAX_VALUE,有界(近似无解),FIFO,使用两个
 *重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待。
 *        - PriorityBlockingQueue 支持优先级排序的无界阻塞队列。默认采用元素自然顺序升序排列。
 *        - DelayQueue 使用优先级队列实现的延迟无界阻塞队列。无界,元素有过期时间,过期的元素才能被取出。
 *        - SynchronousQueue 不存储元素的阻塞队列,也即单个元素的阻塞队列。容量为0,添加任务必须等待取出任务,这个队列相当于通道,
 *不存储元素。
 *        - LinkedTransferQueue 由链表结构组成的无界阻塞队列。
 *        - LinkedBlockingDeque 由链表组成的双向阻塞队列。
 * 
 * 	3. 拒绝策略
 *        - AbortPolicy  直接抛异常阻止系统正常运行
 *        - CallerRunsPolicy  由调用线程处理该任务
 *        - DiscardOldestPolicy 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
 *        - DiscardPolicy  也是丢弃任务,但是不抛出异常。
 */

//线程池源码
public ThreadPoolExecutor(int corePoolSize,		//核心线程数,线程池维护的最小线程,不会被回收
                          int maximumPoolSize,	//最大线程数,corePoolSize已满且未达到maximumPoolSize,可创建新线程接受并执行请求
                          long keepAliveTime,	//空闲线程存活时间,可被回收的线程保留时间
                          TimeUnit unit,		//keepAliveTime的时间单位
                          BlockingQueue<Runnable> workQueue,	/*工作队列,存放待执行任务的队列:当提交的任务数超过核心线程数后,
再提交的任务就存放在工作队列,任务调度时再从队列中取出任务。它仅仅用来存放被execute()方法提交的Runnable任务。工作队列实现了
BlockingQueue接口。*/
                          ThreadFactory threadFactory,	//线程工厂,可以设定线程名和线程编号等
                          RejectedExecutionHandler handler	/*拒绝策略,当线程池线程数已满,并且工作队列达到限制,新提交的任务使用
拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现RejectedExecutionHandler接口*/
                             ) {
     if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
         throw new IllegalArgumentException();
     if (workQueue == null || threadFactory == null || handler == null)
         throw new NullPointerException();
     this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
     this.corePoolSize = corePoolSize;
     this.maximumPoolSize = maximumPoolSize;
     this.workQueue = workQueue;
     this.keepAliveTime = unit.toNanos(keepAliveTime);
     this.threadFactory = threadFactory;
     this.handler = handler;
}
   
//7大参数均被volatile修饰
public class ThreadPoolExecutor extends AbstractExecutorService {
    private final BlockingQueue<Runnable> workQueue;
    private volatile ThreadFactory threadFactory;
    private volatile RejectedExecutionHandler handler;
    private volatile long keepAliveTime;
    // 是否允许核心线程被回收
    private volatile boolean allowCoreThreadTimeOut;
    private volatile int corePoolSize;
    private volatile int maximumPoolSize;
}


import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 线程池工厂工具
 */
public class ThreadPoolFactory {

   /**
    * 生成固定大小的线程池
    *
    * @param threadName 线程名称
    * @return 线程池
    */
   public static ExecutorService createFixedThreadPool(String threadName) {
       AtomicInteger threadNumber = new AtomicInteger(0);
       return new ThreadPoolExecutor(
           // 核心线程数
           desiredThreadNum(),
           // 最大线程数
           desiredThreadNum(),
           // 空闲线程存活时间
           60L,
           // 空闲线程存活时间单位
           TimeUnit.SECONDS,
           // 工作队列
           new ArrayBlockingQueue<>(1024),
           // 线程工厂
           new ThreadFactory() {
               @Override
               public Thread newThread(Runnable r) {
                   return new Thread(r, threadName + "-" + threadNumber.getAndIncrement());
               }
           },
           // 拒绝策略
           new RejectedExecutionHandler() {
               @Override
               public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                   if (!executor.isShutdown()) {
                       try {
                           //尝试阻塞式加入任务队列
                           executor.getQueue().put(r);
                       } catch (Exception e) {
                           //保持线程的中断状态
                           Thread.currentThread().interrupt();
                       }
                   }
               }
           });
   }
   
	 /**
	  * 理想的线程数,使用 2倍cpu核心数
	  */
	 public static int desiredThreadNum() {
	     return Runtime.getRuntime().availableProcessors() * 2;
	 }
}

在这里插入图片描述

Executors中线程池创建的4种方式

  1. newFixedThreadPool:创建的是定长的线程池,可以控制线程最大并发数,超出的线程会在线程队列中等待,使用的是无界队列,核心线程数和最大线程数一样,当线程池中的线程没有任务时候立刻销毁,使用默认线程工厂。
  2. newSingleThreadExecutor:创建的是单线程化的线程池,只会用唯一一个工作线程执行任务,可以指定按照是否是先入先出,还是优先级来执行任务。同样使用无界队列,核心线程数和最大线程数都是1个,同样keepAliveTime为0,可选择是否使用默认线程工厂。
  3. newCachedThreadPool:设定一个可缓存的线程池,当线程池长度超过处理的需要,可以灵活回收空闲线程,如果没有可以回收的才新建线程。没有核心线程数,当线程没有任务60s之后就会回收空闲线程,使用有界队列。同样可以选择是否使用默认线程工厂。
  4. newScheduledThreadPool:支持线程定时操作和周期性操作。

sleep() 和 wait() 有什么区别?

sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的锁没有被释放,其他线程依然无法访问这个对象。

wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程

线程的 run()和 start()有什么区别?为什么我们不能直接调用 run() 方法?

一句话说,run()对应就绪态,start()对应运行态,才是真正运行线程

每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。

start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。

run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。

总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。

可重入与不可重入锁

  1. 不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。实现简单

  2. 可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一(计数器+1)。设计了加锁次数,以在解锁的时候,可以确保所有加锁的过程都解锁了,其他线程才能访问。不然没有加锁的参考值,也就不知道什么时候解锁?解锁多少次?才能保证本线程已经访问完临界资源了可以唤醒其他线程访问了。实现相对复杂。

synchronized 和 volatile 的区别是什么?

  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

synchronized 和 Lock 有什么区别?

  • synchronized是java内置关键字,在jvm层面,Lock是个java类;
  • synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  • synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  • synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
  • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

synchronized 和 ReentrantLock 区别是什么?

  • synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
    • ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
    • ReentrantLock可以获取各种锁的信息
    • ReentrantLock可以灵活地实现多路通知
  • 锁机制不一样
    • ReentrantLock底层调用的是Unsafe的park方法加锁
    • synchronized操作的应该是对象头中mark word

session 和 cookie 有什么区别?

  1. 存储位置不同,cookie在客户端浏览器;session在服务器;
  2. 存储容量不同,cookie<=4K,一个站点最多保留20个cookie;session没有上线,出于对服务器的保护,session内不可存过多东西,并且要设置session删除机制;
  3. 存储方式不同,cookie只能保存ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据;session中能存储任何类型的数据,包括并不局限于String、integer、list、map等;
  4. 隐私策略不同,cookie对客户端是可见的,不安全;session存储在服务器上,安全;
  5. 有效期不同,开发可以通过设置cookie的属性,达到使cookie长期有效的效果;session依赖于名为JESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session达不到长期有效的效果;
  6. 跨域支持上不同,cookie支持跨域;session不支持跨域;

控制反转IOC,依赖注入DI,依赖倒置原则DIP

  • 控制反转 :是一种思想,对象的创建,管理,赋值交由程序管理,需要使用时直接调用
  • 依赖注入:不通过new()的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
    • 依赖注入的3种方式
      • 构造器注入
      • setter注入
      • 注解注入
@RestController
  public class AlarmContactController extends BaseController {
      
      // 这就是大名鼎鼎的DI啊,是不是非常简单!
      @Autowired
      private IAlarmContactService alarmContactService;
  }
  • 依赖倒置原则:开闭原理的实现,简单讲,公共属性抽象出来独立,在其他类需要用时继承并实现其方法

Autowired和Resource

@Resource注解是Java自身的注解,@Autowired注解是Spring的注解.

@Resource注解有两个重要的属性,分别是name和type,如果name属性有值,则使用byName的自动注入策略,将值作为需要注入bean的名字,如果type有值,则使用byType自动注入策略,将值作为需要注入bean的类型.如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。即@Resource注解默认按照名称进行匹配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,按照名称查找,当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

@Autowired注解是spring的注解,此注解只根据type进行注入,不会去匹配name.但是如果只根据type无法辨别注入对象时,就需要配合使用@Qualifier注解或者@Primary注解使用.

Spring Bean 的作用域之间有什么区别?

Spring容器中的bean可以分为5个范围:

  • singleton:这种bean范围是默认的,这种范围确保不管接受多少请求,每个容器中只哟一个bean的实例,单例模式;
  • prototype:为每一个bean提供一个实例;
  • request:在请求bean范围内为每一个来自客户端的网络请求创建一个实例,在请求完毕后,bean会失效并被垃圾回收器回收;
  • session:为每个session创建一个实例,session过期后,bean会随之消失;
  • global-session:global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet公用全局的存储变量的话,那么全局变量需要存储在global-session中。

bean的生命周期

  1. Spring对bean进行实例化;
  2. Spring将值和bean的引用注入到bean对应的属性中;
  3. 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
  4. 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
  5. 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
  6. 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()方法;
  7. 如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,
  8. 如果bean使用initmethod声明了初始化方法,该方法也会被调用;
  9. 如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;

此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;

  1. 如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。

spring循环依赖

是由于在创建bean时A调用B,且B又调用A造成相互依赖

出现情况:

构造器构造bean

setter方法&&多例下创建bean

setter方法&&单利下创建bean(该方法可用三级缓存解决)

解决办法:三级Map缓存

spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。

① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事 务,该设置是最常用的设置。

② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

SpringMVC运行流程

  • 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet(前端控制器)捕获;
  • DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping(处理器映射器)获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
  • DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter;(附注:如果成功获得HandlerAdapter(处理器适配器)后,此时将开始执行拦截器的preHandler(…)方法)
  • 提取Request中的模型数据,填充Handler(处理器,几controller层)入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
    • HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
    • 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
    • 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
    • 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
  • Handler执行完成后,向DispatcherServlet 返回一个ModelAndView(视图解析器)对象;
  • 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
  • ViewResolver 结合Model和View,来渲染视图;
  • 将渲染结果返回给客户端。

HashMap

1.7下采用数组 + 链表,扩容时采用头插法,会产生CPU100%(扩容时出现循环依赖)

1.8下采用数组 + 链表 + 红黑树,扩容时采用尾插法,当链表长度》=8时表位红黑树,当链表长度《6时便会链表,选择8(便捷记忆:在JDK1.8版本更换)是因为8次碰撞几乎不可能发生,选择6是为了避免设为8造成链表与红黑树来回切换浪费资源

1.8没有一开始就用红黑树取代链表,是因为数据量少的时候查询区别不大,但红黑树占用空间更多,变相造成资源浪费;当数据多时,链表查询变慢,红黑树查询更优,

1.7和1.8都遵循:在put方法中发现数组为空才会创建Entry数组。若指定长度不是2的次幂,则会选择大于该数的最近的2次幂数作为长度,如传10,则会初始化16的数组。

@PostConstruct注解

假设类UserController有个成员变量UserService被@Autowired修饰,那么UserService的注入是在UserController的构造方法之后执行的。

如果想在UserController对象生成时候完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入的对象,那么就无法在构造函数中实现。

因此,可以使用@PostConstruct注解来完成初始化,@PostConstruct注解的方法将会在UserService注入完成后被自动调用

public class UserController {
    @Autowired
    private UserService userService;

    public UserController() {
    }

    // 初始化方法
    @PostConstruct
    public void init(){
        userService.userServiceInit();
    }
}

SpringBoot解决跨域

 @Configuration
    public class CorsConfig implements WebMvcConfigurer {
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
                    .allowCredentials(true)
                    .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                    .maxAge(3600);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值