java基础知识

什么是事物

事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合;

事物的四大特性

⑴ 原子性(Atomicity)由on no log日志保证

原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

⑵ 一致性(Consistency)由其他三大特性保证
  一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

⑶ 隔离性(Isolation)由MVCC保证
  隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。

⑷ 持久性(Durability)由内存redo log保证
  持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

数据类型的范围
类型字节范围
byte1字节-128到127
short2字节-2的15次方到2的15次方-1
int4字节-2的31次方到2的31次方-1
long8字节
float4字节
double8字节
char1字节
boolean2字节
面向对象和面向流程的区别

面向流程大多集中的关注点在于流程和过程,而面向对象大多在于有哪些参与者,和各自的作用是什么。

面向对象类别

面向过程思想:步骤清晰简单,第一步做什么,第二步做什么;处理较为简单的问题。

面向对象思想:物以类聚,分类的思维模式,思考问题首先解决问题会需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向对象的思索。

适合处理复杂的问题,适合处理需要多人协作的问题。

OOP(Object Oriented Programming)

面向对象编程的本质:以类的方式组织代码,以对象的组织(封装)数据。

三大特性:封装,继承,多态。抽象

封装,继承:继承基类的方法并做出改变或扩展,多态:所属类的不同,外部对于同一个方法的调用,其执行的逻辑不同。

封装和抽象的区别

抽象是指将业务抽象为软件领域的元素(系统,模块或类);封装则是指定义元素的边界,隐藏实现,开放接口。

抽象是指从众多的事物中抽取出具体共同的,本质性的特征作为一个整体。是共同特质的集合形式。

封装是指通过抽象所得到的数据信息和操作进行结合,使其形成一个有机的整体。对内执行操作,对外隐藏细节和数据信息。

两者的区别在于抽象是一种思维方式,而封装则是一种基于抽象的操作方法。我们通过抽象所得到数据信息及其功能,以封装的技术将其重新聚合,形成一个新的聚合体,也就是两者是合作者的关系。如果没有抽象,封装就无从谈起;如果没有封装;抽象也将没有意义。

接口和抽象类的区别

抽象类可以存在普通成员函数,而接口只能存在public abstract方法;抽象类中的成员变量可以使各种类型的,接口成员变量只能是public static final类型的;抽象类只能继承一个,接口可多个;

关注一个事物本质时使用抽象类,关注一个操作时使用接口;

普通类和抽象类有哪些区别?

普通类不能包含抽象方法,抽象类可以包含抽象方法。

抽象类不能直接实例化,普通类可以直接实例化。

并发三大特性

原子性、有序性、可见性

Java 的几种锁

公平锁/非公平锁、可重入锁、独享锁/共享锁、互斥锁/读写锁、乐观锁/悲观锁、分段锁、偏向锁/轻量级锁/重量级锁、自旋锁;

范式

范式是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”

分为第一范式,第二范式,第三范式

1 NF: 字段是最小的的单元不可再分
2 NF:满足1NF,表中的字段必须完全依赖于全部主键而非部分主键 (一般我们都会做到)
3 NF:满足2 NF,非主键外的所有字段必须互不依赖

4 NF:满足3 NF,消除表中的多值依赖

构造方法

构造器不能被重写和继承,可以被重载和可以声明为private;

不能用在类(非内部类)上的修饰符为

private,protected

Hash Map底层逻辑结构

是由数组,链表,红黑树组成的;

为什么要改成“数组+链表+红黑树”?

主要是为了提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n),而使用红黑树是 O(logn)。

那在什么时候用链表?什么时候用红黑树?

对于插入,默认情况下是使用链表节点。当同一个索引位置的节点在新增后超过8个(阈值8):如果此时数组长度大于等于 64,则会触发链表节点转红黑树节点(treeifyBin);而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容,因为此时的数据量还比较小。

对于移除,当同一个索引位置的节点在移除后达到 6 个,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点。

HashMap 的默认初始容量是多少?HashMap 的容量有什么限制吗?

默认初始容量是16。HashMap 的容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传 9,容量为16。

哈希表冲突

在理想的情况下,每一个 关键字,通过哈希函数计算出来的地址都是不一样的。但是在实际情况中,我们常常会碰到两个关键字key1≠key2,但是f(key1) = f(key2), 这种现象称为冲突,并把key1和key2称为这个散列函数的同义词。

解决冲突办法:开放地址法、再哈希(再散列法)、链地址法、公共溢出区法;

HashMap 的插入流程是怎么样的?

img

HashMap 和Hashtable 的区别

HashMap 允许 key 和 value 为 null,Hashtable 不允许。

HashMap 的默认初始容量为 16,Hashtable 为 11。

HashMap 的扩容为原来的 2 倍,Hashtable 的扩容为原来的 2 倍加 1。

HashMap 是非线程安全的,Hashtable是线程安全的。

HashMap 的 hash 值重新计算过,Hashtable 直接使用 hashCode。

HashMap 去掉了 Hashtable 中的 contains 方法。

HashMap 继承自 AbstractMap 类,Hashtable 继承自 Dictionary 类。

微服务架构

all in one的架构方式,我们拥有所有的的功能单元放在一个应用里面,然后我们把整个应用部署到服务器上,如果负载能力不行,我们将整个应用进行水平复制,然后再负载均衡。

微服务架构,就是打破all in one的架构方式,把每个功能元素独立出来,把独立出来的的功能元素动态组合,需要的功能元素才去拿来组合,需要多一些是可以整合多个功能元素,所以微服务架构是对功能元素进行复制。

节省了调用资源

每个功能都是一份服务可以进行调用和调试;

重载、重写、隐藏的区别

重载:是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。

隐藏:是指派生类的函数屏蔽了与其同名的基类函数,注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。

重写(覆盖):是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。

&和&&的区别

&&:逻辑与运算符。当运算符左右两边的表达式都为 true,才返回 true。同时具有短路性,如果第一个表达式为 false,则直接返回 false。

&:逻辑与运算符、按位与运算符。

按位与运算符:用于二进制的计算,只有对应的两个二进位均为1时,结果位才为1 ,否则为0。

逻辑与运算符:& 在用于逻辑与时,和 && 的区别是不具有短路性。所在通常使用逻辑与运算符都会使用 &&,而 & 更多的适用于位运算。

String 是 Java 基本数据类型吗

不是,Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type)

基本数据类型:数据直接存储在栈上

引用数据类型区别:数据存储在堆上,栈上只存储引用地址

String 类可以继承吗?

不行,String 类使用 final 修饰,无法被继承

String s = “xyz” 和 String s = new String(“xyz”) 区别

两个语句都会先去字符串常量池中检查是否已经存在 “xyz”,如果有则直接使用,如果没有则会在常量池中创建 “xyz” 对象。

另外,String s = new String(“xyz”) 还会通过 new String() 在堆里创建一个内容与 “xyz” 相同的对象实例。

所以前者其实理解为被后者的所包含。

String,StringBuilder和StringBuffer的区别

String是一个长度不可变的字符序列

StringBuffer:线程安全,StringBuilder:线程不安全

因为StringBulider是线程不安全的,所有公开方法都是没有加锁的,但是StringBuffer是线程安全的,有公开方法都是同步的,所以毫无疑问,StringBuilder的速度要大于StringBuffer。

char 型变量中能不能存贮一个中文汉字,为什么?

char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。

数组有没有length()方法?String有没有length()方法?

答:java中数组是没有length()方法的,只有length属性,数组array.length返回的是该数组的长度。字符串String是有length()方法的,str.length()返回的是该字符串的长度。

== 和 equals 的区别是什么?

==:运算符,用于比较基础类型变量和引用类型变量。

对于基础类型变量,比较的变量保存的值是否相同,类型不一定要相同。

equals :Object 类中定义的方法,通常用于比较两个对象的值是否相等。

equals 在 Object 方法中其实等同于 ==,但是在实际的使用中,equals 通常被重写用于比较两个对象的值是否相同。

== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比
较,只是很多类重新了 equals 方法,比如 String 、 Integer 等把它变成了值比较,所以一般情况下
equals 比较的是值是否相等。

=的区别

对于string,number等基础类型,=是有区别的

1)不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等

2)同类型比较,直接进行“值”比较,两者结果一样

基础类型与高级类型,=是有区别的

1)对于==,将高级转化为基础类型,进行“值”比较

2)因为类型不同,===结果为false

对于Array,Object等高级类型,=是没有区别的

进行“指针地址”比较

两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?

不对。hashCode() 和 equals() 之间的关系如下:

当有 a.equals(b) == true 时,则 a.hashCode() == b.hashCode() 必然成立,

反过来,当 a.hashCode() == b.hashCode() 时,a.equals(b) 不一定为 true。

什么是反射

反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为反射机制。

深拷贝和浅拷贝区别是什么

数据分为基本数据类型引用数据类型。基本数据类型:数据直接存储在栈中;引用数据类型:存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里。

浅拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。

深拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。

深拷贝相比于浅拷贝速度较慢并且花销较大。

并发和并行有什么区别

并发:两个或多个事件在同一时间间隔发生。

并行:两个或者多个事件在同一时刻发生。

并行是真正意义上,同一时刻做多件事情,而并发在同一时刻只会做一件事件,只是可以将时间切碎,交替做多件事情。

构造器是否可被重写

Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到⼀个类中有多个构造函数的情况。

Java 静态变量和成员变量的区别

成员变量存在于堆内存中。静态变量存在于方法区中。

成员变量与对象共存亡,随着对象创建而存在,随着对象被回收而释放。静态变量与类共存亡,随着类的加载而存在,随着类的消失而消失。

成员变量所属于对象,所以也称为实例变量。静态变量所属于类,所以也称为类变量。

成员变量只能被对象所调用静态变量可以被对象调用也可以被类名调用。

初始化考察,请指出下面程序的运行结果。
public class InitialTest {
    public static void main(String[] args) {
        A ab = new B();
        ab = new B();
    }
}
class A {
    static { // 父类静态代码块
        System.out.print("A");
    }
    public A() { // 父类构造器
        System.out.print("a");
    }
}
class B extends A {
    static { // 子类静态代码块
        System.out.print("B");
    }
    public B() { // 子类构造器
        System.out.print("b");
    }
}

执行结果:ABabab,两个考察点:

1)静态变量只会初始化(执行)一次。

2)当有父类时,完整的初始化顺序为:父类静态变量(静态代码块)->子类静态变量(静态代码块)->父类非静态变量(非静态代码块)->父类构造器 ->子类非静态变量(非静态代码块)->子类构造器 。

例2:

class A {

    static {
        System.out.print("1");
    }

    public A() {
        System.out.print("2");
    }
}

class B extends A{

    static {
        System.out.print("a");
    }

    public B() {
        System.out.print("b");
    }
}

public class Hello {

    public static void main(String[] args) {
        A ab = new B();
        ab = new B();
    }

}

执行结果:1a2b2b,创建对象时构造器的调用顺序是:先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。

Java中有几种类型的流?

字节流和字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在java.io 包中还有许多其他的流,主要是为了提高性能和使用方便。关于Java的I/O需要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。另外Java中的流不同于C#的是它只有一个维度一个方向。

你在项目中哪些地方用到了XML?

XML的主要作用有两个方面:数据交换信息配置。在做数据交换时,XML将数据用标签组装成起来,然后压缩打包加密后通过网络传送给接收者,接收解密与解压缩后再从XML文件中还原相关信息进行处理,XML曾经是异构系统间交换数据的事实标准,但此项功能几乎已经被JSON(JavaScript Object Notation)取而代之。当然,目前很多软件仍然使用XML来存储配置信息,我们在很多项目中通常也会将作为配置信息的硬代码写在XML文件中,Java的很多框架也是这么做的,而且这些框架都选择了dom4j作为处理XML的工具,因为Sun公司的官方API实在不怎么好用。

在进行数据库编程时,连接池有什么作用?

由于创建连接和释放连接都有很大的开销(尤其是数据库服务器不在本地时,每次建立连接都需要进行TCP的三次握手,释放连接需要进行TCP四次握手,造成的开销是不可忽视的),为了提升系统访问数据库的性能,可以事先创建若干连接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭连接,从而避免频繁创建和释放连接所造成的开销,这是典型的用空间换取时间的策略

使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能?

要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的setFetchSize()方法指定每次抓取的记录数(典型的空间换时间策略);要提升更新数据的性能可以使用PreparedStatement语句构建批处理,将若干SQL语句置于一个批处理中执行。

什么是DAO模式?

DAO(Data Access Object)顾名思义是一个为数据库或其他持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作。在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在一个公共API中。用程序设计语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储。DAO模式实际上包含了两个模式,一是Data Accessor(数据访问器),二是Data Object(数据对象),前者要解决如何访问数据的问题,而后者要解决的是如何用对象封装数据。

简述一下面向对象的"六原则一法则"

单一职责原则、开闭原则、依赖倒转原则、接口隔离原则、合成聚合复用原则、迪米特法则

简述一下你了解的设计模式。

答:所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。

重载(Overload)和重写(Override)的区别

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

重载:一个类中有多个同名的方法,但是具有有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)。

重写:发生在子类与父类之间,子类对父类的方法进行重写,参数都不能改变,返回值类型可以不相同,但是必须是父类返回值的派生类。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为

抽象类(abstract class)和接口(interface)有什么区别?

抽象类只能单继承,接口可以多实现。

抽象类可以有构造方法,接口中不能有构造方法。

抽象类中可以有成员变量,接口中没有成员变量,只能有常量(默认就是 public static final)

Error 和 Exception 有什么区别

Error 和 Exception 都是 Throwable 的子类,用于表示程序出现了不正常的情况。区别在于:

Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题,比如内存溢出,不可能指望程序能处理这样的情况。

Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题,也就是说,它表示如果程序运行正常,从不会发生的情况。

Java 中的 final 关键字有哪些用法

修饰类:该类不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract 和 final。

修饰方法:该方法不能被子类重写。

修饰变量:该变量必须在声明时给定初值,而在以后只能读取,不可修改。 如果变量是对象,则指的是引用不可修改,但是对象的属性还是可以修改的

阐述 final、finally、finalize 的区别

其实是三个完全不相关的东西,只是长的有点像。。

final :修饰类:该类不能再派生出新的子类,不能作为父类被继承;修饰方法:该方法不能被子类重写

finally :finally 是对 Java 异常处理机制的最佳补充,通常配合 try、catch 使用,用于存放那些无论是否出现异常都一定会执行的代码。在实际使用中,通常用于释放锁、数据库连接等资源,把资源释放方法放到 finally 中,可以大大降低程序出错的几率。

finalize :Object 中的方法,在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

try、catch、finally 考察,请指出下面程序的运行结果

例1:

public class TryDemo {
    public static void main(String[] args) {
        System.out.println(test());
    }
    public static int test() {
        try {
            return 1;
        } catch (Exception e) {
            return 2;
        } finally {
            System.out.print("3");
        }
    }
}

执行结果:31

try、catch。finally 的基础用法,在 return 前会先执行 finally 语句块,所以是先输出 finally 里的 3,再输出 return 的 1

例2:

	public static int demo5() {
        try {
            return printX();
        }
        finally {
            System.out.println("finally trumps return... sort of");
        }
    }
    public static int printX() {
        System.out.println("X");
        return 0;
    }

//结果:
X
finally trumps return... sort of
0

例3:

public class TryDemo {
    public static void main(String[] args) {
        System.out.println(test1());
    }
    public static int test1() {
        try {
            return 2;
        } finally {
            return 3;
        }
    }
}
//结果:
3

try 返回前先执行 finally,结果 finally 里不按套路出牌,直接 return 了,自然也就走不到 try 里面的 return 了

例4:

public class TryDemo {
    public static void main(String[] args) {
        System.out.println(test1());
    }
    public static int test1() {
        int i = 0;
        try {
            i = 2;
            return i;
        } finally {
            i = 3;
        }
    }
}
//结果:
2

在执行 finally 之前,JVM 会先将 i 的结果暂存起来,然后 finally 执行完毕后,会返回之前暂存的结果,而不是返回 i,所以即使这边 i 已经被修改为 3,最终返回的还是之前暂存起来的结果 2。

JDK 1.8之后有哪些新特性

接口默认方法:Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可

Stream API:用函数式编程方式在集合类上进行复杂操作的工具,配合Lambda表达式可以方便的对集合进行处理

方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器

日期时间API :Java 8 引入了新的日期时间API改进了日期时间的管理

新工具:新的编译工具,如:Nashorn 引擎 jjs、 类依赖分析器 jdeps

wait() 和 sleep() 方法的区别

来源不同:sleep() 来自 Thread 类,wait() 来自 Object 类

对于同步锁的影响不同:sleep() 不会该表同步锁的行为,如果当前线程持有同步锁,那么 sleep 是不会让线程释放同步锁的。wait() 会释放同步锁,让其他线程进入 synchronized 代码块执行。

使用范围不同:sleep() 可以在任何地方使用。wait() 只能在同步控制方法或者同步控制块里面使用,否则会抛 IllegalMonitorStateException。

恢复方式不同:两者会暂停当前线程,但是在恢复上不太一样。sleep() 在时间到了之后会重新恢复;wait() 则需要其他线程调用同一对象的 notify()/nofityAll() 才能重新恢复。

线程的 sleep() 方法和 yield() 方法有什么区别

线程执行 sleep() 方法后进入超时等待(TIMED_WAITING)状态,而执行 yield() 方法后进入就绪(READY)状态。

sleep() 方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程运行的机会;yield() 方法只会给相同优先级或更高优先级的线程以运行的机会。

线程

线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。
我们看到的进程的切换,切换的也是不同进程的主线程
多线程可以让同一个进程同时并发处理多个任务,相当于扩展了进程的功能。

Java 虚拟机栈(后面简称栈)是线程私有的,所以他的生命周期与当前线程是一样的,栈是用来描述方法执行的一个内存模型,因为每个方法在执行的同时,都会创建一个栈帧,而这个栈帧里面,又存储着局部变量表,操作数栈,动态链接,方法出口等一系列信息

编写多线程程序有几种实现方式

通常来说,可以认为有三种方式:1)继承 Thread 类;2)实现 Runnable 接口;3)实现 Callable 接口。

其中,Thread 其实也是实现了 Runable 接口。Runnable 和 Callable 的主要区别在于是否有返回值。

Thread 调用 start() 方法和调用 run() 方法的区别

run():普通的方法调用,在主线程中执行,不会新建一个线程来执行。

start():新启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到 CPU 时间片,就开始执行 run() 方法

线程的五大状态

新建、就绪、运行、阻塞、死亡

线程安全

线程安全又可以成为内存安全,堆是共享内存可以被所有的线程访问。

多个线程进行访问一个对象的时候,如果不行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的。

堆:分为全局堆,局部堆。全局堆是没有被分配的空间,局部堆是已经分配的空间在系统初始化的时候进行分配。主要目的是存放实例对象;需要及时收回不然容易导致内存泄漏。

栈:是线程独有的,保存运行状态额局部自动变量。

如何检测死锁

死锁的四个必要条件:

1)互斥条件:进程对所分配到的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

2)请求和保持条件:进程已经获得了至少一个资源,但又对其他资源发出请求,而该资源已被其他进程占有,此时该进程的请求被阻塞,但又对自己获得的资源保持不放。

3)不可剥夺条件:进程已获得的资源在未使用完毕之前,不可被其他进程强行剥夺,只能由自己释放。

4)环路等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中 Pi 等待的资源被 P(i+1) 占有(i=0, 1, …, n-1),Pn 等待的资源被 P0占有

怎么预防死锁

预防死锁的方式就是打破四个必要条件中的任意一个即可。

1)打破互斥条件:在系统里取消互斥。若资源不被一个进程独占使用,那么死锁是肯定不会发生的。但一般来说在所列的四个条件中,“互斥”条件是无法破坏的。因此,在死锁预防里主要是破坏其他几个必要条件,而不去涉及破坏“互斥”条件。。

2)打破请求和保持条件:1)采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待。 2)每个进程提出新的资源申请前,必须先释放它先前所占有的资源。

3)打破不可剥夺条件:当进程占有某些资源后又进一步申请其他资源而无法满足,则该进程必须释放它原来占有的资源。

4)打破环路等待条件:实现资源有序分配策略,将系统的所有资源统一编号,所有进程只能采用按序号递增的形式申请资源

为什么要使用线程池?直接new个线程不是很舒服?

如果我们在方法中直接new一个线程来处理,当这个方法被调用频繁时就会创建很多线程,不仅会消耗系统资源,还会降低系统的稳定性,容易把系统搞崩

如果我们合理的使用线程池,则可以避免把系统搞崩的窘境。总得来说,使用线程池可以带来以下几个好处:

  1. 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 增加线程的可管理型。线程是稀缺资源,使用线程池可以进行统一分配,调优和监控。
线程池的创建方式
  1. Threadlocal
线程池的参数

corePoolsize代表核心线程数,就是正常情况下创建工作的线程数;

maxinumPoolSize 代表最大线程数;

keepALiveTime 表示超出核心线程数之外的线程的空闲存活时间;

workQueue 用来存放待执行的任务;

ThreadFactory 线程工厂,用来生产线程任务;

Handler 任务拒绝策略

线程池的处理流程

线程池执行任务,判断核心线程是否已满未满创建核心线程执行,已满判断任务队列是否已满,未满将任务放入队列中,已满判断最大线程数是否达到,未满创建临时线程,已满任务拒绝策略。

线程池的底层工作原理

线程池内部是通过队列和线程实现;

List、Set、Map三者的区别

List(对付顺序的好帮手): List 接口存储一组不唯一(可以有多个元素引用相同的对象)、有序的对象。

Set(注重独一无二的性质):不允许重复的集合,不会有多个元素引用相同的对象。

Map(用Key来搜索的专业户): 使用键值对存储。Map 会维护与 Key 有关联的值。两个 Key可以引用相同的对象,但 Key 不能重复,典型的 Key 是String类型,但也可以是任何对象。

List

1.可重复。
2.可以为空。
3.有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。

Set接口

1.不可重复。
2.可以为空。
3.无序容器,无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。

Map接口

1.以键值对的形式进行存储。
2.键不可以重复,值可以重复。
3.键只能有一个null键,值可以有多个null键。

ArrayList和LinkedList的区别

ArrayList:基于动态数组,连续内存存储,适合下标访问;

LinkedList:基于链表,可以存储在分散内存,适合做数据插入机删除操作,不适合查询;

java虚拟机

JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。

先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

Java虚拟机中有哪些类加载器

启动类加载器(Bootstrap ClassLoader):

这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。

扩展类加载器(Extension ClassLoader):

这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器(Application ClassLoader):

这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

自定义类加载器

用户自定义的类加载器。

类加载的过程

类加载的过程包括:加载、验证、准备、解析、初始化,其中验证、准备、解析统称为连接。

Java 内存结构(运行时数据区)

程序计数器:线程私有。一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空。

Java虚拟机栈:线程私有。它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

本地方法栈:线程私有。本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

Java堆:线程共享。对大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

方法区:与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(构造方法、接口定义)、常量、静态变量、即时编译器编译后的代码(字节码)等数据。方法区是JVM规范中定义的一个概念,具体放在哪里,不同的实现可以放在不同的地方。

运行时常量池:运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

Java 中有哪几种常量池?

现在我们经常提到的常量池主要有三种:class 文件常量池、运行时常量池、字符串常量池。

介绍下垃圾收集机制(在什么时候,对什么,做了什么)

垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收

垃圾判别方法

在什么时候

在触发GC的时候,具体如下,这里只说常见的 Young GC 和 Full GC。

触发Young GC:当新生代中的 Eden 区没有足够空间进行分配时会触发Young GC。

触发Full GC:

当准备要触发一次Young GC时,如果发现统计数据说之前Young GC的平均晋升大小比目前老年代剩余的空间大,则不会触发Young GC而是转为触发Full GC。(通常情况)
如果有永久代的话,在永久代需要分配空间但已经没有足够空间时,也要触发一次Full GC。
System.gc()默认也是触发Full GC。
heap dump带GC默认也是触发Full GC。
CMS GC时出现Concurrent Mode Failure会导致一次Full GC的产生。
对什么?

对那些JVM认为已经“死掉”的对象。即从GC Root开始搜索,搜索不到的,并且经过一次筛选标记没有复活的对象。

做了什么?

对这些JVM认为已经“死掉”的对象进行垃圾收集,新生代使用复制算法,老年代使用标记-清除和标记-整理算法。

Public、private、protected

public的访问权限:当前类,子类,当前类所在的包下,以及其他包下都可以进行访问。
protected的访问权限:除了当前类所在的包下、以及子类都可以访问,其他的包下都不能进行访问。(可以用肥水不流外人田记忆,哈哈)
默认的话:只能当前类和当前类所在的包下可以访问,当前类的子类和其他包下都不能进行访问。
private的访问权限:只有当前类可以访问(你可以理解成,加了private的话,这个人的钱就只能他自己知道他有多少钱,别人都不知道)
访问的权限依次是递减的。

img

Object类中有哪些方法
  1. wait();
  2. toString();
  3. notify();
  4. notifyAll();
  5. clone();
  6. equals();
  7. hashcode();
  8. finalize();
垃圾收集有哪些算法,各自的特点?

标记 - 清除算法

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

复制算法

为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。

标记 - 整理算法

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法

当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。在老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清理或者标记—整理算法来进行回收。

什么 是内存泄漏和内存溢出,有什么解决办法?

内存泄漏:其实就是当我们在创建对象或者是变量之后,并且该对象或者变量在程序中都不再使用了,但是又没有办法回收这些没用的对象和变量。而且还一直占用着系统的内存空间,此时的现象就是内存泄漏。

解决办法:

我们在使用静态变量的时候尽量减少对静态变量的使用,因为静态变量是存储在方法区(也就是永久代),不会被回收
如果在程序中有不用的对象,应该及时进行释放。
内存溢出:其实就是当我要申请一块内存的时候,但是此时虚拟机不能够给我提供这么大的内存,就说明内存溢出了,比如出现OutOfMemoryError,StackOverflowError 这种错误信息的时候

解决办法:

修改 JVM 启动参数, 直接增加内存。 (-Xms, -Xmx 参数一定不要忘记加。一般要将-Xms 和-Xmx 选项设置为相同, 以避免在每次 GC 后调整堆的大小; 建议堆的最大值设置为可用内存的最大值的 80%)。
根据日志进行分析哪里的问题所在,进行排查。

jdk动态代理,静态代理

静态代理

其实静态代理的最终目的就是为了扩展原本的对象方法的功能,通过代理对象类在去保证真实对象功能的前提下,再此基础上去扩展一些新的功能或者说是去增加一些繁琐且必须要去做的事情
但是如果当我们的业务接口过多的话,我们的代理对象也会变的很多,而且代理对象和真实对象都必须要实现同一个真正干活的业务接口。这也是他的缺点
jdk的动态代理

而其实动态代理就是为了解决上一个静态代理的缺点,他可以不用代理对象和真是对象都去同时实现同一个业务接口。
而动态的代理的核心就是实现InvocationHandler 接口类重写invoke方法Object invoke(Object proxy, Method method, Object[] args) :在代理实例上处理方法调用并返回结果。 和public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) 这两个核心方法。
还有一点核心的就是使用ava.lang.reflect.Proxy类,他提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类Object invoke(Object proxy, Method method, Object[] args)。Ojbect proxy:表示需要代理的对象,Method method:表示要操作的方法, Object[] args:method方法所需要传入的参数(可能没有为,null.也可能有多个)

什么是序列化和反序列化?序列化的底层怎么实现的?

什么是序列化和反序列化

Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转换成Java对象的过程

序列化的底层怎么实现的

其实就是利用了java的IO流来进行实现的,

MySQL中的都有哪些字节类型,都占用多少个字节?

数字类型的:

tinyint ,占用1个字节,他的数的范围是(-127~128)
smallint,占用2个字节,他的可存储的范围是在(-32768~32767)
int ,占用4个字节,这里其实是和java中的数据类型互通的
bigint,占用8个字节,范围更大
float,占用4个字节
double,占用8个字节

volatile和synchronized特点

首先需要理解线程安全的两个方面:执行控制内存可见

执行控制的目的是控制代码执行(顺序)及是否可以并发执行。

内存可见控制的是线程执行结果在内存中对其它线程的可见性。根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。

synchronized关键字解决的是执行控制的问题,它会阻止其它线程获取当前对象的监控锁,这样就使得当前对象中被synchronized关键字保护的代码块无法被其它线程访问,也就无法并发执行。更重要的是,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作,都happens-before于随后获得这个锁的线程的操作。

volatile关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接刷到主存,即保证了变量的可见性。这样就能满足一些对变量可见性有要求而对读取顺序没有要求的需求。

synchronize的偏向锁,轻量锁,重量锁,自旋锁

偏向锁是获取到该线程的id,该线程下次获取到该锁是就自动获取到了

轻量锁是偏向锁的升级,如果有第二个线程来竞争锁就会变成轻量锁,是通过线程的自旋完成的;

什么是高并发

高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。

高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。

  • 响应时间:系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200 ms,这个200 ms就是系统的响应时间。
  • 吞吐量:单位时间内处理的请求数量。
  • QPS:每秒响应请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。
  • 并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。
如何提升系统的并发能力

互联网分布式架构设计,提高系统并发能力的方式,方法论上主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)。

垂直扩展:提升单机处理能力。垂直扩展的方式又有两种:

(1)增强单机硬件性能,例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128 G;

(2)提升单机架构性能,例如:使用Cache来减少IO次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间;

在互联网业务发展非常迅猛的早期,如果预算不是问题,强烈建议使用“增强单机硬件性能”的方式提升系统并发能力,因为这个阶段,公司的战略往往是发展业务抢时间,而“增强单机硬件性能”往往是最快的方法。

不管是提升单机硬件性能,还是提升单机架构性能,都有一个致命的不足:单机性能总是有极限的。所以互联网分布式架构设计高并发终极解决方案还是水平扩展。

水平扩展:只要增加服务器数量,就能线性扩充系统性能。水平扩展对系统架构设计是有要求的,如何在架构各层进行可水平扩展的设计,以及互联网公司架构各层常见的水平扩展实践,是本文重点讨论的内容。

大用户量访问情况下项目的各种优化方案

1、HTML静态化

2、图片服务器分离

3、数据库集群、库表散列

4、缓存

5、镜像

6、负载均衡

控制反转

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做**依赖注入(Dependency Injection,简称DI**),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

链表反转
//递归方法
class Solution {
    public ListNode reverseList(ListNode head) {
        return reverse(null, head);
    }
 
    private ListNode reverse(ListNode prev, ListNode cur) {
        if (cur == null) {
            return prev;
        }
        ListNode temp = null;
        temp = cur.next;// 先保存下一个节点
        cur.next = prev;// 反转
        // 更新prev、cur位置
        prev = cur;
        cur = temp;
        return reverse(prev, cur);
    }
}
~~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值