目录
int 和 Integer 有什么区别;Integer的值缓存范围
什么是Java序列化和反序列化;如何实现Java序列化;或者请描述Serializable接口的作用
说说 CountDownLatch、CyclicBarrier 原理和区别
ThreadLocal 原理分析;ThreadLocal为什么会出现OOM,出现的深层次原理
什么是 ABA 问题;出现 ABA 问题 JDK 是如何解决的
存储引擎的 InnoDB 与 MyISAM 区别、优缺点、使用场景
Java基础:
面向对象的特征:继承、封装和多态
- 封装:
封装,就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏;
- 继承:
它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展;
通过继承创建的新类成为"子类"或者"派生类";
被继承的类成为"基类"、"父类"或"超类";
继承的过程,就是从一般到特殊的过程;
一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
继承概念的实现方式有三类:
- 实现继承:是指使用基类的属性和方法而无需额外编码的能力;
- 接口继承:是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
- 可视继承:是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。
- 多态:
是允许你将父对象设置成为一个或者更多的它的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
多态概念的实现方式有两种:
- 覆盖:是指子类重新定义父类的虚函数的做法;
- 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不用,或许参数类型不用、或许两者都不同)。
重载和重写的区别
- override(重写)
- 方法名、参数、返回值相同;
- 子类方法不能缩小父类方法的访问权限;
- 子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常);
- 存在于父类和子类之间;
- 方法被定义为final的不能被重写。
- overload(重载)
- 参数类型、个数、顺序至少有一个不相同;
- 不能重载只有返回值不同的方法名;
- 存在于父类和子类、同类型中。
区别点 | 重载 | 重写(覆写) |
---|---|---|
英文 | Overloading | Overiding |
定义 | 方法名称相同,参数的类型或个数不同 | 方法名称、参数类型、返回值类型全部相同 |
权限 | 对权限没要求 | 被重写的方法不能拥有更严格的权限 |
范围 | 发生在一个类中 | 发生在继承类中 |
int 和 Integer 有什么区别;Integer的值缓存范围
两者之间的区别:
- Integer是int的包装类;int是基本数据类型;
- Integer变量必须实例化后才能使用;int不需要;
- Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值;
- Integer的默认值是null;int的默认值是0。
Integer的值缓存范围:-128 ~ 127
说说反射的用途及实现
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能成为Java语言的反射机制。
反射机制的原理:
- 当 Student 类 new 一个对象的时候,会通知 JVM 去本地加载 Student.class 这个二进制文件;
- JVM 在本地磁盘寻找这个文件,找到之后就把它加载到 JVM 内存中,在加载的同时会生成一个对象来映射这个 class 文件,该对象中存储着 Student.class 的信息,包括字段、方法等,将该对象放在 JVM 的一块内存空间中;
- 在 JVM 内存中为 Student 开辟一块空间,来存储 student1。
反射的本质就是当获取到表示 Student.class 的对象后,反向获取 Student 类的信息。
Java反射框架提供以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射设置可以调用private);
- 在运行时调用任意一个对象的方法。
Http 请求的 GET 和 POST 方式的区别
GET产生一个TCP数据包:
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
POST产生两个TCP数据包:
对于POST方式的请求,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
MVC设计思想
首先,MVC不是一种设计模式,而是一种设计思想,接下来看下两个概念的区别:
- 设计模式:是一种固定的方法,不灵活,有特定的使用场景;
- 设计思想:是一种思想,比较灵活,由多种设计模式组合实现。
接下来说说MVC的设计思想:
- M(Model):主要功能提供数据(主要用来提供数据,并不关心数据让谁显示(Controller 负责给M要数据,然后控制数据让哪一个View来显示));
- V(View):主要功能是展示数据(主要有数据即可,不关心数据来源);
- C(Controller):主要功能协调V层与M层,作为V层与M层沟通的桥梁。
什么是Java序列化和反序列化;如何实现Java序列化;或者请描述Serializable接口的作用
- 序列化:把对象转换为字节序列的过程称为对象的序列化;
- 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
如何实现Java序列化:实现serializable接口
- ObjectOutputStream(对象输出流):
它的 writeObject(Object obj) 方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;
- ObjectInputStream(对象输入流):
它的 readObject() 方法从一个源输入流中读取字节序列,再把他们反序列化为一个对象,并将其返回。
Colletcion类库中常用类
Collection是List、set父接口,不是Map父接口。
- JavaList
- List集合:ArrayList LinkList Vector 等;
- Vector:是List接口下线程安全的结婚;
- List是有序的;
- ArrayList 和 LinkList 的区别:
- ArrayList:适合于查询较多的场合,实现了基于动态数组的数据结构;
- LinkList:适合于插入较多的场合,实现了基于链表的数据结构(双向链表)。
- JavaMap
- Map集合:HashMap HashTable ConcurrentHashMap LinkedHashMap 等;
- HashMap 不是线程安全的;
- HashTable ControllerHashMap SynchronizedMap 是线程安全的;
- HashMap 的键值都可以为NULL,HashTable不行;
- 按添加顺序使用LinkedHashMap,按自然顺序使用TreeMap,自定义排序用TreeMap;
- HashSet 和 HashTree 的区别:
- HashSet:哈希表实现,数据是无序的,可以放入一个NULL值;
- TreeSet:二叉树实现,数据是自动排好序的,不允许放入NULL值。
进程和线程:
线程和进程的概念
- 线程:单个进程中执行的每个任务就是一个线程。线程是进程中执行运算的最小单位;
- 进程:是执行中的一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程;进程表示资源分配的基本概念,又是调度原型的基本单位,是系统中的并发执行的单位。
并行和并发的概念
- 并行:指应用能够同时执行不同的任务;
- 并发:指应用能够交替执行不同的任务。
创建线程的方式及实现
- 继承 Thread 类创建线程;
A)定义 Thread 类的子类,并重写该类的 run() 方法,该 run() 方法的方法体就代表了线程要完成的任务,因此把 run() 方法称为执行体;
B)创建 Thread 子类的实例,即创建了线程对象;
C)调用线程对象的 start() 方法来启动该线程。
- 实现 Runnable 接口创建线程;
A)定义 runnable 接口的实现类,并重写该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体;
B)创建 Runnable 接口实现类的实例,并依此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象;
C)调用线程对象的 start() 方法来启动该线程。
- 使用 Callable 和 Future 创建线程。
A)创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值;
B)创建 Callable 实现类的实例,使用 Future Task 类来包装 Callable 对象,该 Future Task 对象封装了该 Callable 对象的 call() 方法的返回值;
C)使用 Future Task 对象作为 Thread 对象的 target 创建并启动新线程;
D)调用 Future Task 对象的 get() 方法来获得子线程执行结束后的返回值。
三种方式的比较:
1> 通过 Runnable 和 Callable 创建多线程:
优势:
A)线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类;
B)在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势:
C)编程稍微复杂,如果要访问当前线程,必须使用 Thread.currentThread() 方法。
D) Runnable 和 Callable 的区别:
Runnable | Callable | |||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
重写的方法是 run() | 重写的方法是call() | |||||||||||||||||||||||||||||||||
Runnable的任务是不能返回值的 | Callable的任务执行后可返回值 | |||||||||||||||||||||||||||||||||
run()方法不可以抛出异常 | call() 方法可以抛出异常 | |||||||||||||||||||||||||||||||||
运行 Callable 任务可以看到一个 Future 对象,表示异步计算的结果。
优势: 如果要访问当前线程,则无需使用 Thread.currentThread() 方法,使用 this即可; 劣势: 已经继承了 Thread 类,不能再继承其他父类。 进程间通信的方式进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
管道是一种半双工(即数据只能在一个方向上流动)的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常指父子进程关系。
将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们称为高级管道方式。
有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号量(Semaphore)是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及统一进程内不用线程之间的同步手段。
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是对快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
套接字(socket):套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。 说说 CountDownLatch、CyclicBarrier 原理和区别CountDownLatch: 是同步辅助类,它可以指定一个计数值,在并发环境下由线程进行减1操作,当计数值变为0后,被 await() 方法阻塞的线程将会唤醒,实现线程间的同步。 一个或N个线程等待其它线程的关系。 CyclicBarrier: 是同步辅助类,它允许一组线程互相等待,直到所有线程都达到某个特公共屏障点(也可以叫同步点),即相互等待的线程都完成调用 await() 方法,所有被屏障拦截的线程才会继续运行 await() 方法后面的程序。 各个线程内部相互等待的关系。 两者的区别:
两者的使用场景,下面的文章说的很清楚: CountDownLatch 和 CyclicBarrier 的使用场景 说说 Semaphore 原理Semaphore(信号量) 是用来控制同时访问特定资源的线程数量,它通过协调各个线程,保证合理的使用公共资源。 线程可以通过 acquire() 方法来获取信号量的许可。当信号量中没有可用的许可的时候,线程阻塞,直到有可用的许可为止。线程可以通过 release() 方法释放它持有的信号量的许可。 Semaphore 内部基础AQS的共享模式,所以实现都委托给了Sync类。 Semaphore 有两种模式:
调用 acquire() 的顺序就是获取许可证的顺序,遵循FIFO;
为抢占式的,可能一个新的获取线程恰好在一个许可证释放时得到这个许可证,而前面还有等待的线程。 说说 Exchanger 原理主要用于两个工作线程之间交换数据。 ThreadLocal 原理分析;ThreadLocal为什么会出现OOM,出现的深层次原理ThreadLocal 是线程的局部变量,是每一个线程所单独持有的,其他线程不能对其进行访问。通常是类中的 private static 字段,是对该字段初始值的一个拷贝,它们希望将状态与某一个线程(例如用户ID或者事务ID)相关联。 ThreadLocal 为什么会出现OOM:
ThreadLocal 的实现是这样的:每个 Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的 Object; ThreadLocal 里面使用了一个存在弱引用的 map ,map 的类型是 ThreadLocal.ThreadLocalMap。Map 中的 key 为一个 ThreadLocal 实例本身,而这个 key 使用弱引用指向 ThreadLocal 。当 ThreadLocal 实例置为 null 后,没有任何强引用指向 ThreadLocal 实例,所以ThreadLocal 将会被 GC 回收。但是我们的 value 却不能回收,而这块 value 永远不会被访问到。所以存在着内存泄漏。因为存在一条从 Current Thread 链接过来的强引用,只有当 Thread 结束以后, Current Thread 就不会存在栈中,强引用断开,Current Thread、Map Value 将全部GC回收。 如何避免内存泄漏: 每次使用完 ThreadLocal,都调用它的 remove() 方法,清除数据。 讲讲线程池的实现原理提交一个任务到线程池中,线程池的处理流程如下:
线程池的几种实现方式
创建一个可缓存的线程池,如果线程池的长度超过处理的需要,可以灵活回收空闲线程,若无可回收,则新建线程。
创建一个定长线程池,可以控制线程最大并发数,超出的线程会在队列中等待。
创建一个定长线程池,支持定时、周期性的任务执行。
创建一个单线程化的线程池,只会用唯一一个工作线程执行任务。
线程池的相关信息可以看下这个链接:线程池相关概念 线程的生命周期;状态是如何转移的线程的生命周期:
当创建一个 Thread 类的一个实例(对象)时,此线程进入新建状态(未被启动); 例如:Thread t1 = new Thread();
线程已经被启动,正在等待被分配给 CPU 时间片,也就是说此时线程正在就绪队列中排队等候得到 CPU 资源; 例如:ti.start();
线程获得 CPU 资源正在执行任务(run() 方法),此时除非此线程自动放弃 CPU 资源或者有优先级更高的线程进入,线程将一直运行到结束。
由于某种原因导致正在运行的线程让出 CPU 并暂停自己的执行,即进入阻塞状态。 正在睡眠:调用 sleep(long t) 方法 可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。 正在等待:调用 wait() 方法(调用 motify() 方法回到就绪状态)。 被另一个线程所阻塞:调用 suspend() 方法(调用 resume() 方法恢复)。
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这是线程不可能再进入就绪状态等待执行。 自然终止:正常运行 run() 方法后终止; 异常终止:调用 stop() 方法让一个线程终止运行。 锁机制:什么是线程安全?如何保证线程安全?线程安全:是指要控制多个线程对某个资源的有序访问或修改,而这些线程之间没有产生冲突。 线程安全问题都是由 全部变量 和 静态变量 引起的。 造成线程安全问题的主要诱因有两点:
保证线程安全的方法
重入锁的概念;重入锁为什么可以防止死锁?重入锁:指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取对象上的锁。而其他的线程是不可以的。 死锁:如果一个进程集合里面的每个进程都在等待这个集合中的其他一个进程(包括自身)才能继续往下执行,若无外力他们将无法推进。这个情况就是死锁。处于死锁状态的进程成为死锁进程。 产生死锁的四个条件
进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源;
进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但是又对自己获得的的资源保持不放;
是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完成后自己释放;
是指进程发生死锁后,必然存在一个进程 -- 资源之间的环形链。 如何检查死锁通过 jConsole(JDK 自带的图形化界面工具) 检查死锁 volatile 实现原理volatile 定义: Java 编程语言允许线程访问共享变量,为了确保共享变量能被准备和一致的更新,线程应该确保通过排它锁单独获得这个变量
synchronized 实现原理(对象监视器)依赖 JVM 实现。 每个对象都有一个监视器锁(monitor)。当 montior 被占用时,就会处于锁定状态,线程执行 monitorenter 指令时尝试获取 monitor 的所有权,过程如下:
synchronized 与 lock 的区别
AQS 同步队列用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态,通过内置FIFO队列来完成资源获取线程的排队工作。 CAS 无锁的概念;乐观锁和悲观锁
总是认为不会产生并发问题,每次取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁。但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或者 CAS 操作实现;
总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。 synchronized 的思想就属于悲观锁。 常见的原子操作类什么是 ABA 问题;出现 ABA 问题 JDK 是如何解决的ABA 问题: 如果另一个线程修改 V 值,假设值原来是 A,先修改成 B,再修改回成 A,当前线程的 CAS 操作无法分辨当前 V 值是否发生过变化。 如何解决 ABA 问题: 用 AtomicStampedReference 解决 ABA 问题。 乐观锁的业务场景及实现方式乐观锁(Optimistic Lock): 乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。 Java 8 并发包下常见的并发类偏向锁、轻量级锁、重量级锁、自旋锁的概念
为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径;
为了在无多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗;
通过对象内部的监视器(montior)实现,其中 montior 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高;
就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否很快释放锁。 数据库:DDL、DML、DCL 分别指什么
explain 命令explain 命令显示了 Mysql 如何使用索引来处理 SELECT 语句以及连接表。可以帮助选择更好的索引和写出更优化的查询语句。 使用方法:在 SELECT 语句前加上 explain 就可以了。 explain 列说明: |