知识总结正式版

目录

JVM

JVM内存模型

​编辑

   1. 堆        

   2. 栈      

        栈和栈帧

        栈帧的结构​编辑

   1. 局部变量表

   2. 操作数栈     

栈帧运行方式

   3. 方法区      

java 创建对象的几种方式

   1. 采用new

   2. 通过反射(使用Class类的newInstance方法)

   3. 采用clone

   4. 通过反序列化机制

java当中的四种引用

   1. 强引用

   2. 软引用

   3. 弱引用

   4. 虚引用

为什么要有不同的引用类型

接口和抽象类

两者的比较

为何要创造接口和抽象类      

什么时候使用抽象类和接口

final类

   final, finalize和finally的不同之处

   final有哪些用法

静态方法

   作用

   java是什么时候加载静态方法的

   优缺点

继承与实现

   继承的特性(继承父类方法,并扩展)     

   实现的特性(实现接口的所有方法)

String和StringBuffer,字符串常量池

   String

   字符串常量池

   1.乐观锁和悲观锁

        1. 悲观锁        

           定义

           悲观锁分类

                悲观锁的实现

                悲观锁的优点和缺点              

        2. 乐观锁

                定义

                乐观锁的实现

                乐观锁的优点和缺点            

   2. synchronized

        四种锁状态

        Synchonized锁的对象类型                

        Java对象头(monitor)

        synchronized锁状态

           1. 偏向锁

                1. 引入背景

                2.偏向锁升级

                3.偏向锁的撤销

        2. 轻量级锁

                1. 轻量级锁加锁

                2. 轻量级锁解锁             

        3. 重量级锁

        4. 锁膨胀方向

        5. 锁的优缺点对比

        synchronized 和Lock的区别

        synchronized和ReentrantLock的区别

        可重入锁

3. 死锁

   死锁产生的原因   

        1. 系统资源的竞争

        2. 进程推进顺序非法

        3.信号量使用不当也会造成死锁。

   死锁产生的必要条件

        1. 互斥使用

        2. 不可剥夺

        3. 请求和保持

        4. 循环等待

   处理死锁的三种基本方法

        预防死锁

        避免死锁

java常见问题

   1. switch中能否使用string做参数

   2. java中==和equal()的区别

   3. a=a+b与a+=b有什么区别吗

   4. int和Integer的区别

   5. wait、notify和notifyAll的区别   

   6. wait()与sleep()的区别

   7. 如何正确的使用wait()?使用if还是while

   8. Sleep(0)的妙用     

   9. SimpleDateFormat是线程安全的吗

   10.Java 中,Serializable 与 Externalizable 的区别        

数据库

1. 数据库事务的四大特性(ACID)

   原子性(Atomicity)

   一致性(Consistency)

   隔离性(Isolation)

   持久性(Durability)

   2. Mysl数据库读写锁

 

   3. 事务的隔离级别

        1. 读未提交(READ-UNCOMMITTED)

        2. 读已提交(READ-COMMITTED)

        3. 可重复读(REPEATABLE-READ)

        4. 可串行化(SERIALIZABLE)

设计思想

面向对象

面向对象的三个特征

封装

继承

多态

Java 实现多态有 3 个必要条件

实现多态,有二种方式

java中的多态实现

   1. 接口实现 (implements )

   2.继承普通父类,重写普通方法 (extends)

   3.继承抽象父类,重写抽象方法

多态的作用

AOP和OOP的区别

生产者-消费者模式


JVM

JVM内存模型

   1. 堆        

        存储的是对象,每个对象都包含一个与之对应的class

        JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定

   2. 栈      

        每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象)

        每个栈中的数据(原始类型和对象引用)都是私有的

        栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令),数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会自动消失

        栈和栈帧

                栈帧(Stack Frame)是一段连续的栈空间,用于支持虚拟机进行方法调用和方法执行的数据结构

        栈帧的结构
   1. 局部变量表

        变量值的存储空间,用于存放方法参数和局部变量

        

   2. 操作数栈     

操作数据的栈空间,在里面进行数据的操作

传递数据,

int a =1, 1先入栈,再保存到局部变量表

相加

Int c= a+b,a,b数据入栈,后入先出出栈相加,得出结果入栈,再保存到局部变量表

栈帧运行方式

   3. 方法区      

        静态区,跟堆一样,被所有的线程共享

        方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量,字符串常量池

java 创建对象的几种方式

   1. 采用new

        String a = new String();

   2. 通过反射(使用Class类的newInstance方法)

        反射

        通过反射获取私有成员

                field.setAccessible(true)

   3. 采用clone

   4. 通过反序列化机制

java当中的四种引用

   1. 强引用

        如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

        

   2. 软引用

        在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收

        当内存不足,就会回收软引用,因为aRef=null的时候,已经不再是强引用了

   3. 弱引用

        具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。

   4. 虚引用

        顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收

为什么要有不同的引用类型

        第一是可以让程序员通过代码的方式决定某些对象的生命周期;

        第二是有利于JVM进行垃圾回收

通过软引用实现Java对象的高速缓存:比如我们创建了一Person的类,如果每次需要查询一个人的信息,哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这将引起大量Person对象的消耗,并且由于这些对象的生命周期相对较短,会引起多次GC影响性能。此时,通过软引用和 HashMap 的结合可以构建高速缓存,提供性能。

接口和抽象类

两者的比较

   相同点:
         1、都不能被实例化。
        2、接口的实现类和抽象类的子类只有全部实现了接口或者抽象类中的方法后才可以被实例化。
   不同点:
        1、接口只能定义抽象方法不能实现方法,抽象类既可以定义抽象方法,也可以实现方法。
        2、单继承,多实现。接口可以实现多个,只能继承一个抽象类。
        3、接口强调的是功能,抽象类强调的是所属关系。
        4、接口中的所有成员变量 为public static final, 静态不可修改,当然必须初始化。接口中的所有方法都是public abstract 公开抽象的。而且不能有构造方法。抽象类就比较自由了,和普通的类差不多,可以有抽象方法也可以没有,可以有正常的方法,也可以没有。

为何要创造接口和抽象类      

抽象类

比如我们熟悉的泰迪,哈巴,二哈,阿拉斯加,秋田犬等等大小形态有很大区别,我们怎么把他们抽象一下呢?那就是他们都有一些本质上相同的东西那就是他们都是狗,是狗就有耳朵,尾巴,四肢等等我们把这些给抽象出来,至于耳朵是啥样的,尾巴是长是短,颜色是啥,这就需要子类实现狗这个抽象方法了。

这就是我们之前面向对象的一个基本特性,抽象。

那这个抽象类的作用是什么呢?
1、用于隐藏
对类型进行隐藏,我们可以构造出一个固定的一组行为的抽象描述,一个行为可以有任意个可能的具体实现方式。这个抽象的描述就是抽象类。(参考多态)

2、用于拓展对象的行为功能
这一组任意个可能的具体实现表现为所有可能的子类,模块可以操作一个抽象类,由于模块依赖于一个固定的抽象类,那么他是不允许修改的。同时通过这个抽象类进行派生,拓展此模块的行为功能。(参考开放闭合原则)

接口

一种特殊的抽象类,但是比抽象类更加抽象

那么接口的作用是什么呢?
1、Java单继承的原因所以需要曲线救国 作为继承关系的一个补充。
2、把程序模块进行固化的契约,降低偶合。把若干功能拆分出来,按照契约来进行实现和依赖。(依赖倒置原则)
3、定义接口有利于代码的规范。(接口分离原则)

最后:

abstract class 表示的是is a关系,interface表示的是like a关系。
抽象类强调的是从属关系,接口强调的是功能。

什么时候使用抽象类和接口

如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口

抽象类适合用来定义某个领域的固有属性,也就是本质,接口适合用来定义某个领域的扩展功能。

如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类

用户(抽象类)-系统用户/普通用户(子类)

用户功能(接口)-登录/鉴权

final类

   final, finalize和finally的不同之处

        final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。

        finalize 方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用 finalize 没有保证。

        finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常

   final有哪些用法

final也是很多面试喜欢问的地方,能回答下以下三点就不错了:
1.被final修饰的类不可以被继承 
2.被final修饰的方法不可以被重写 
3.被final修饰的变量不可以被改变。如果修饰引用,那么表示引用不可变,引用指向的内容可变

静态方法

   作用

        静态方法只要定义了类,不必建立类的实例就可使用

   java是什么时候加载静态方法的

        类的初始化中的准备阶段。

   优缺点

  

继承与实现

   继承的特性(继承父类方法,并扩展)     

        子类拥有父类非 private 的属性、方法。

        子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

        子类可以用自己的方式实现父类的方法。

        提高了类之间的耦合性

   实现的特性(实现接口的所有方法)

String和StringBuffer,字符串常量池

String和StringBuffer主要区别是性能:String是不可变对象,每次对String类型进行操作都等同于产生了一个新的String对象,然后指向新的String对象。所以尽量不在对String进行大量的拼接操作,否则会产生很多临时对象,导致GC开始工作,影响系统性能。

StringBuffer(.append())是对对象本身操作,而不是产生新的对象,因此在有大量拼接的情况下,我们建议使用StringBuffer。

   String

        String str=new String("a"+"b")创建了几个对象?

  1. "a"在字符串常量池创建,这是第一个对象
  2. "b"在字符串常量池创建,这是第二个对象
  3. “a”+"b"组合"ab"在字符串常量池创建,这是第三个对象
  4. new String(),在堆中分配内存,里面存着这字符串"ab"在字符串常量池中的地址,这是第四个对象

注意:str 存储在栈中,里面存储着指向堆中new String()的地址(引用不是对象)

        String str1=“ab" 创建了几个对象?

                1个," ab "在字符串常量池创建,这是第一个对象

        String str2=“a”+“b”创建了个对象?

               1个, java在编译时会将“a”+“b”自动转换成” ab”,而不会经过加法的过程,也就是说str1=str2.

   字符串常量池

        String:字符串常量池 - Java - SegmentFault 思否

   1.乐观锁和悲观锁

        1. 悲观锁        
           定义

                修改数据之前先锁定数据,再修改的方式(先取锁再访问

                总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行

        加锁操作,当其他线程想要访问数据时,都需要阻塞挂起

           悲观锁分类

                共享锁和排他锁

                共享锁【shared locks】又称为读锁,简称S锁。顾名思义,共享锁就是多个事务对于同

        一数据可以共享一把锁,都能访问到数据,但是只能读不能修改

                排他锁【exclusive locks】又称为写锁,简称X锁。顾名思义,排他锁就是不能与其他锁

        并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包

        括共享锁和排他锁,但是获取排他锁的事务是可以对数据行读取和修改

                悲观锁的实现

                1. 传统的关系型数据库使用这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

                2. Java 里面的同步 synchronized 关键字的实现

                悲观锁的优点和缺点              

                优点 :悲观锁实际是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。

                缺点: 但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死

        锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事

        务处理完才可以处理那行数据

        2. 乐观锁
                定义

                假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据

        的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。

        乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量

                乐观锁的实现

                CAS机制

                版本号机制

                当某个线程查询数据时,将该数据的版本号一起查出来;

                当该线程更新数据时,判断当前版本号与之前读取的版本号是否一致,如果一致才进行

        操作。

                1. 表内增加version字段

                2. 取出记录时,获取当前version ,更新时,带上这个version

                3. 执行更新时, set version = newVersion where version = oldVersion,如果version不

        对,就更新失败

                乐观锁的优点和缺点            

                优点: 乐观锁相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接

        做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。

                缺点: 适用的场景受到了更多的限制

                当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,因为悲观锁会锁住代码块

        或数据,其他线程无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源。

                当竞争激烈(出现并发冲突的概率大)时,悲观锁更有优势,因为乐观锁在执行更新时频繁

        失败,需要不断重试,浪费CPU资源。

   2. synchronized

        四种锁状态

                无锁状态 偏向锁状态 轻量级锁状态 重量级锁状态

        Synchonized锁的对象类型                

                Java中每一个对象都可以作为锁。具体有如下三种形式:

                对于修饰普通方法,锁是当前实例对象。

                对于修饰静态方法,锁是当前类的Class对象。

                对于修饰代码块,锁是synchronized括号里配置的对象

        Java对象头(monitor)

                synchronized锁的信息,是存在Java对象头部的Mark word

        

         Mark word

        synchronized锁状态
           1. 偏向锁
                1. 引入背景

                在大多实际环境下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获取,那么在同一个线程反复获取所释放锁中,其中并还没有锁的竞争,那么这样看上去,多次的获取锁和释放锁带来了很多不必要的性能开销和上下文切换。

为了解决这一问题,引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和推出同步块时不需要进行CAS操作来加锁和解锁。只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。

  • 如果测试成功,表示线程已经获得了锁。
  • 如果测试失败,则测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没设置,则使用CAS竞争锁(竞争什么?);如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

a线程获得锁,会在a线程的的栈帧里创建lockRecord,在lockRecord里和锁对象的MarkWord里存储线程a的线程id.以后该线程的进入,就不需要cas操作,只需要判断是否是当前线程

                2.偏向锁升级

                一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头MarkWord成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS进行操作。一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象是偏向状态,这时表明在这个对象上已经存在竞争了,操作系统检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程;如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁(对象被锁定),则偏向锁升级为轻量级锁(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了(对象未锁定),则可以将对象回复成无锁状态,然后重新偏向

                3.偏向锁的撤销

                偏向锁使用了一种等待竞争出现才会释放锁的机制。所以当其他线程尝试获取偏向锁时,持有偏向锁的线程才会释放锁。但是偏向锁的撤销需要等到全局安全点(就是当前线程没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着。如果线程不处于活动状态,直接将对象头设置为无锁状态。如果线程活着,JVM会遍历栈帧中的锁记录,栈帧中的锁记录和对象头要么偏向于其他线程,要么恢复到无锁状态或者标记对象不适合作为偏向锁

        2. 轻量级锁

                在线程执行同步块之前,JVM会先在当前线程的栈帧中创建一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝

                1. 轻量级锁加锁

                线程在执行同步块之前,JVM会将对象头中的Mark Word复制到栈帧的锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS【Compare and Swap,比较并交换,即比较两个值,如果他们两者相等就把他们交换】将对象头中的Mark Word替换为指向栈帧锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

                2. 轻量级锁解锁             

                轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

                1. a线程获得锁,栈帧锁记录中的lock record指针指向锁对象的对象头mark word.再让mark word 指向lock record.这就是获取了锁。

                 2. 轻量级锁,b线程在锁竞争时,发现锁已经被a线程占用,则b线程不进入内核态,让b线程自旋,执行空循环,等待a线程释放锁。如果,完成自旋策略还是发现a线程没有释放锁,或者让c线程占用了。则b线程试图将轻量级锁升级为重量级锁。

        

        3. 重量级锁

                就是让争抢锁的线程从用户态转换成内核态。让cpu借助操作系统进行线程协调,当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cpu。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长

        4. 锁膨胀方向

                无锁 → 偏向锁 → 轻量级锁 → 重量级锁 (此过程是不可逆的)

        5. 锁的优缺点对比

        整个synchronized锁流程如下:

                1.检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁

                2.如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获

        得偏向锁,置偏向标志位1

                3.如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。

                4.当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获

        得锁

                5.如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

                6.如果自旋成功则依然处于轻量级状态。

                7.如果自旋失败,则升级为重量级锁。

        synchronized 和Lock的区别
  • synchronized 是 java 内置关键字,在 jvm 层面,Lock 是个 java 类
  • synchronized 无法判断是否获取锁的状态,Lock 可以判断是否获取到锁
  • synchronized 会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock 需在 finally 中手工释放锁(unlock() 方法释放锁),否则容易造成线程死锁
  • 性能方面,如果竞争不激烈的时候,synchronize 比 Lock 的性能高,如果竞争激烈(多线程)的时候,Lock 的效率会比synchronize 高,所以Lock 锁适合大量同步的代码的同步问题,synchronized 锁适合代码少量的同步问题

synchronized 的锁可重入、不可中断、非公平而 Lock锁 可重入、可判断可公平或非公平(两者皆可)公平锁即:在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁非公平锁即:就随机的获取,谁运气好,cpu时间片轮到哪个线程,哪个线程就能获取锁

        synchronized和ReentrantLock的区别

        ReentrantLock有Lock的特性,同时ReentrantLock 可以对获取锁的等待时间进行设置,这样就避免了死锁限时等待

响应中断,可以手动中断线程

        可重入锁

如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

之前我们也有提到过一个词,叫锁计数器。重入锁实现机制就是基于一个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的线程持有者,同时将锁计数器置为1;此时其它线程请求该锁,就只能等待,而该锁持有者却可以再次请求锁,同时锁计数器会递增,当线程退出同步代码块时,计数器会递减,直至锁计数器为0,则释放。

所以,如果ReentrantLock不手动释放锁,就会造成死锁

3. 死锁

多个线程之间因为相互竞争资源,形成循环等待的僵局,造成无限期阻塞的情况

   死锁产生的原因   
        1. 系统资源的竞争

通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在 运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞争 才可能产生死锁,对可剥夺资源的竞争是不会引起死锁的。

        2. 进程推进顺序非法

进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程 P1、P2分别保持了资源R1、R2,而进程P1申请资源R2,进程P2申请资源R1时,两者都 会因为所需资源被占用而阻塞。

        3.信号量使用不当也会造成死锁。

进程间彼此相互等待对方发来的消息,结果也会使得这 些进程间无法继续向前推进。例如,进程A等待进程B发的消息,进程B又在等待进程A 发的消息,可以看出进程A和B不是因为竞争同一资源,而是在等待对方的资源导致死锁。

   死锁产生的必要条件
        1. 互斥使用

                即当资源被一个线程使用(占有)时,别的线程不能使用。

        2. 不可剥夺

                资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

        3. 请求和保持

                即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

        4. 循环等待

                即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了环路等待

   处理死锁的三种基本方法

        处理死锁的基本方法有:预防死锁、避免死锁、检测死锁三种方法        

        预防死锁

预防死锁通过破坏死锁产生的四个必要条件来达到预防死锁产生的目的。但采用这种方法时不能破坏互斥条件,因为它是由设备的固有特性决定的,破坏会影响程序的正常运行

  1. 一次性分配所有资源,这样就不会再请求资源了破坏”请求和保持“条件
  2. 只要有一个资源得不到分配,也不给该进程分配其他资源
  3. 当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源破坏”不剥夺“条件
  4. 系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反破坏”环路等待“条件
        避免死锁

        避免死锁可以转换为避免系统进入不安全状态

                1. 银行家算法

                2. 安全性检查算法


java常见问题


   1. switch中能否使用string做参数

        在idk 1.7之前,switch只能支持byte, short, char, int或者其对应的封装类以及Enum类型。从idk 1.7之后switch开始支持String

   2. java中==和equal()的区别

== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,是否是指相同一个对象

equals用来比较的是两个对象的内容是否相等

equals方法是基类Object中的方法,在Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象

String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等

其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。

  总结来说:

1)对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;

如果作用于引用类型的变量,则比较的是所指向的对象的地址

2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量

如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;

诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

   3. a=a+b与a+=b有什么区别吗

a+=b隐式的将加操作的结果类型强制转换为持有结果的类型

隐式转换示例:

byte b=1;

b=b+10;//编译会报错, b是byte类型,而10是int类型,int类型不能隐式转换为byte

b+=10;//class文件会编译成:b=(byte)(b+10),b先转基本类型相加,再强转byte赋值回去

   4. int和Integer的区别

        Integer是int的包装类,int则是java的一种基本数据类型

   5. wait、notifynotifyAll的区别   

        wait、notify、notifyAll是Object对象的属性,并不属于线程。我们先解释这三个的一个很重要的概念

        wait:使持有该对象的线程把该对象的控制权交出去,然后处于等待状态(这句话很重要,也就是说当调用wait的时候会释放锁并处于等待的状态)

        notify:通知某个正在等待这个对象的控制权的线程可以继续运行(这个就是获取锁,使自己的程序开始执行,最后通过notify同样去释放锁,并唤醒正在等待的线程)

        notifyAll:会通知所有等待这个对象控制权的线程继续运行(和上面一样,只不过是唤醒所有等待的线程继续执行)

   6. wait()与sleep()的区别

        调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁

        sleep()睡眠后不出让系统资源,wait让其他线程可以占用CPU

        sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒.而wait()需要配合notify()或者notifyAll()使用

   7. 如何正确的使用wait()?使用if还是while

        wait() 方法应该在循环里面调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好

   8. Sleep(0)的妙用     

        Thread.Sleep(0) 并非是真的要线程挂起0毫秒,意义在于这次调用Thread.Sleep(0)的当前线程确实的被冻结了一下,让其他线程有机会优先执行。Thread.Sleep(0) 是你的线程暂时放弃cpu,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作

假如A、B两个线程为合作关系,A线程处理一些原始数据,数据处理到一定程度,交给B线程处理,在A处理原始数据的时候,B也要做一些准备工作,所以A、B是并发的,但是B做好准备之后,需要等待A处理好那些数据,接过A的数据继续处理,因此,这个等待,如果A不使用信号或者等待条件来通知B的话,那么B必须一直轮询,查看A是否已完成,B线程所做的这个轮询是否会一直占用CPU来做无用的循环查看呢?因此B这个时候占用的cpu时间片做的是无用功,因此,这里sleep(0)就有作用,当B查看到A没处理完数据的时候,B马上sleep(0)交出B的时间片,让操作系统调度A来运行(假设只有A、B两个线程),那么这个时候,A就会得到充分的时间来处理它的数据,这就是它的一个应用。

   9. SimpleDateFormat是线程安全的吗

        非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如 将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time 库。

   10.Java 中,Serializable 与 Externalizable 的区别        

        Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高、脆弱而且不安全。(持久化保存)

被Serializable接口声明的类的对象的内容都将被序列化,如果现在用户希望自己指定序列化的内容,则可以让一个类实现Externalizable接口

数据库

1. 数据库事务的四大特性(ACID)

   原子性(Atomicity)

        所有操作要么全部成功,要么全部失败回滚

   一致性(Consistency)

        原子性中规定方法中的操作都执行或者都不执行,但并没有说要所有操作一起执行(一起更新那就乱套了,要哪个结果?),所以操作的执行也是有先后顺序的,那我们要是在执行一半时查询了数据库,那我们会得到中间的更新的属性?答案是不会的,一致性规定事务提交前后只存在两个状态,提交前的状态和提交后的状态,绝对不会出现中间的状态。

   隔离性(Isolation)

        当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰

   持久性(Durability)

        当一个事务提交了之后那这个数据库状态就发生了改变,哪怕是提交后刚写入一半数据到数据库中,数据库宕机(死机)了,那当你下次重启的时候数据库也会根据提交日志进行回滚,最终将全部的数据写入

   2. Mysl数据库读写锁

 

        对于同一个数据,读读不互斥,读写互斥,写写互斥

读写互斥

        从锁的粒度,我们可以分成两大类:

        表锁

                开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低

        行锁

                开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高

不同的存储引擎支持的锁粒度

        InnoDB行锁和表锁都支持!

        MyISAM只支持表锁!

        InnoDB只有通过索引条件检索数据才使用行级锁,否则,InnoDB将使用表锁

也就是说,InnoDB的行锁是基于索引的!

   3. 事务的隔离级别

        1. 读未提交(READ-UNCOMMITTED)

                最低的隔离级别,允许读取尚未提交的数据变更,可能造成脏读、不可重复读、幻读。

        2. 读已提交(READ-COMMITTED)

                允许读取并发事务已经提交的数据,可以避免脏读,但是可能造成不可重复、幻读。

        3. 可重复读(REPEATABLE-READ)

                对同一字段多次读取的结果都是一致的,除非本身事务修改,可以避免脏读和不可重复读,但是可能造成幻读。

        4. 可串行化(SERIALIZABLE)

                最高的隔离级别,完全服从ACID的隔离级别,所以的事务依次执行,可以避免脏读、不可重复读、幻读。

MySQL InnoDB存储引擎默认的事务隔离级别是可重复读(REPEATABLE-READ)

设计思想

面向对象

面向对象的三个特征

     封装,继承,多态

封装

封装是把(定义)过程和数据包围起来,对数据的访问只能通过已定义的接口。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。

封装是一种(实现方式)信息隐藏技术在java中通过关键字private,protected和public实现封装

什么是封装?封装把对象的所有组成部分组合在一起,封装定义程序如何引用对象的数据,封装(作用)实际上使用方法将类的数据隐藏起来,控制用户对类的修改和访问数据的程度。 适当的封装可以让程式码更容易理解和维护,也强了程式码的安全性

继承

面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力

这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。比如可以先定义一个类叫车,车有以下属性:车体大小,颜色,方向盘,轮胎,而又由车这个类派生出轿车和卡车两个类,为轿车添加一个小后备箱,而为卡车添加一个大货箱。

多态

指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义

Java 实现多态有 3 个必要条件

继承、重写和向上转型

Map<K, V> asd=new HashMap<K, V>();

多态的写法,使用父类引用指向子类对象(向上转型),可以访问父类方法(通过 super. 来调用),也可以访问子类继承或隐藏的成员变量,或者调用子类继承的方法(父类方法)或者子类重写的实例方法

实现多态,有二种方式

重写,重载(X)。

重写,是指子类重新定义父类的虚函数的做法。

重载(X),是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

其实,重载的概念并不属于“面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关!引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚邦定,它就不是多态。”

那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。

java中的多态实现
   1. 接口实现 (implements )

   2.继承普通父类,重写普通方法 (extends)

   3.继承抽象父类,重写抽象方法

多态的作用
  1. 不必编写每一子类的功能调用,可以直接把不同子类当父类看,屏蔽子类间的差异,提高代码的通用率/复用率

  2. 父类引用可以调用不同子类的功能,提高了代码的扩充性和可维护性

AOP和OOP的区别

  1. AOP: (Aspect Oriented Programming) 面向切面编程。是目前软件开发中的一个热点,也是Spring框架中容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。

  2. OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。 而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。举个简单的例子,对于“雇员”这样一个业务实体进行封装,自然是OOP的任务,我们可以为其建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用AOP设计思想对“雇员”进行封装将无从谈起。

生产者-消费者模式

生产者生产数据到缓冲区中,消费者从缓冲区中取数据。

如果缓冲区已经满了,则生产者线程阻塞;

如果缓冲区为空,那么消费者线程阻塞

  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值