JAVA面试知识点
1.mysql事务
mysql事务主要用于处理操作量大,复杂度高的数据。 比如在一个学生管理系统中,你删除一个学生的数据,需要删除这个学生的个人信息,还要删除该学生相关的课程信息,住宿信息等等,这些数据库的操作语句就构成一个事务。
在mysql中,只有使用了Innodb引擎的数据库或表才能支持事务。
事务处理可用来维护数据库的完整性,要么全部执行,要么全部不执行。
事务通常用来管理insert,update,delete语句。
事务必须满足四个条件(ACID),原子性(Atomicity,或称不可分割性)即一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,事务在执行过程中发生错误,会发生回滚(Rollback)到事务开始前的状态,就像这个事务从来没被执行过一样;
一致性(Consistency)即在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离型(Isolation,又称独立性)即数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read commited)、可重复读(repetable read)和串行化(Serializable)。
持久性(Durability)即事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
在Mysql命令行的默认设置下,事务都是自动提交的,即执行SQL语句后就会马上执行COMMIT操作。因此要显式地开启一个事务必须使用命令BEGIN或START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,来禁止使用当前会话的自动提交。
2.Mysql的Myisam引擎和Innodb引擎
InnoDB:
- 具有事务,支持4个事务隔离级别,回滚,崩溃修复能力和多版本并发的事务安全,包括ACID。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。
- InnoDB不支持全文索引,如果一定要用的话,最好使用sphinx等搜索引擎。不过新版本的InnoDB已经支持了。
- InnoDB支持行级锁,InnoDB的行锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB同样会锁全表。
- InnoDB,基于磁盘的资源是InnoDB表空间数据文件和它的日志文件,InnoDB表的大小只受限于操作系统文件的大小。
- InnoDB使用的是聚簇索引,就是将数据存储与索引放到了一块,找到索引也就找到了数据。
- InnoDB读写阻塞与事务隔离级别相关。
MyISAM:
- MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。
- MyISAM对中文的支持不是很好。
- MyISAM支持表级锁定。
- MyISAM在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型, “.frm"文件存储表定义,数据文件的扩展名为”.MYD",索引文件的扩展名是".MYI"。MyISAM表是保存成文件的形式,在跨平台的数据转移中使用MyISAM存储会省区不少的麻烦。
- MyISAM使用的是非聚簇索引,将数据存储于索引分开结构,索引结构的叶子节点只想了数据的对应行,MyISAM通过key_buffer把索引先缓存到内存中,当需要访问数据时,在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不再key_buffer命中时速度慢的问题。
- MyISAM读写互相阻塞:不仅会在写入的时候阻塞读取,还会在读取的时候阻塞写入,但读本身并不会阻塞另外的读。
3.MySQL锁总结
- 共享锁(读锁):其他事务可以读,但不能写。
- 排他锁(写锁):其他事务不能读取,也不能写。
- 表级锁:MyISAM和MEMORY存储引擎采用的是表级锁。开销小,加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低。存储引擎通过一次性同时获取所有需要的锁以及总是按相同的顺序获取表锁来避免死锁。表级锁更适合于以查询为主,并发用户少,只有少量按索引条件更新数据的应用,如Web应用。
- 页面锁:BDB存储引擎采用的是页面锁,但也支持表级锁。开销和加锁时间介于表锁和行锁之间,会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般。
- 行级锁:InnoDB存储引擎即支持行级锁,也支持表级锁,默认情况下采用行级锁。开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度也最高。行级锁只在存储引擎层实现,MySQL服务器层没有实现。行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理系统。
4.MySQL索引的数据结构
mysql索引的底层数据结构用的是B+树。
- 二叉树:如果是规律性数据,比如 12345,存储容易形成线性结构,数据规模太大,查询太慢。
- 红黑树:存在自平衡问题,虽然不会单边增长,但是数据量太大,数据树的高度是不可控的,向下检索很慢。
- hash:通过hashcode通过位置指针多次定位,某些情况下很快,但是没法实现范围检索,比如id>10,这个条件检索就没法使用hashcode来查询。
- B树(B-Tree):在红黑树的基础上,每个节点可以存储多个数据。横向增加数据个数,解决了纵向数据太高的问题。B树有个很重要的属性度:每个节点最多可以存储多少个数据。比如定义一个B树的某个节点的度为4,最大存储容量是15/16.
- B+树:在B树的基础上在非叶子节点上只存放了key,这样就大大节省了空间。而叶子节点存放key+data,并且key是按照从左自右递增的链表形式,通过next指正就可以很快找到需要的数据。
5.B+树的缺点
B+树最大的性能问题在于会产生大量的随机IO,主要存在以下两种情况:
- 主键不是有序递增的,导致每次插入数据产生大量的数据迁移和空间碎片。
- 即使主键是有序递增的,大量写请求的分布仍是随机的。
6. 主键索引的唯一索引的区别
主键是一种约束,唯一索引是一种索引,两者在本质上是不同的。
主键创建后一定包含一个唯一性索引,唯一性索引并不一定就是主键。
唯一性索引列允许空值,而主键列不允许为空。
主键列在创建时,已经默认为空值+唯一索引了。
主键可以被其他表引用为外键,而唯一索引不能。
一个表最多只能创建一个主键,但可以创建多个唯一索引。主键更适合那些不容易更改的唯一标识,如自动递增列、身份证号等。
7. MySQL存储引擎
Mysql5.5.5以后InnoDB为默认的存储引擎
MySQL5.7支持的存储引擎有InnoDB、MyISAM、Memory、Merge、Archive、Federated、CSV、BLACKHOLE等。
8. 进程和线程的区别
根本区别:
- 进程是对运行时程序的封装,操作系统调度及资源分配的最小单位,实现了操作系统的并发。
- 线程是进程的子任务,是CPU调度和执行的基本打我,实现进程内部的并发。
- 每个线程独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。但是共享同一地址空间(动态内存,映射文件,代码)和内核资源。
开销上的区别:
- 进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销(CPU环境的保存及调度);
- 线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小(只用设置少量寄存器内容)
内存上的区别:
- 系统会为每个进程分配不同的内存空间,而不会为线程分配内存,其使用所属进程的资源。
- 同一进程的多个线程共享代码段(TEXT)、数据段(BSS、DATA)、扩展段(堆栈);但是每个线程拥有自己的栈段,又叫运行时段,用来存放所有局部变量和临时变量。
9. 当线程共享进程的程序和数据时,如何保证各个线程之间互不干扰相互独立呢?
可以使用互斥锁,或者互斥锁搭配条件变量或者信号量。
互斥锁只有两种状态,即上锁和解锁。
原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统包成了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;
唯一性:如果一个线程锁定了互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量。
非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何CPU资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。
10. java怎么保证线程同步(安全)的
- 方法一:使用同步代码块
/**
* @author: Fuwei Feng
* @version: 2020/4/22
*/
public class SyncThreadDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
MyRunnable mr2 = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable {
private int tickets = 10;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// 方法一使用同步代码块
if(tickets > 0) {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " : " + "第" + (tickets--) + "张票售出");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
- 方法二:使用同步方法达到线程安全
/**
* @author: Fuwei Feng
* @version: 2020/4/22
*/
public class SyncThreadDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable {
private int tickets = 10;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// 方法一使用同步代码块
// if(tickets > 0) {
// synchronized (this) {
// System.out.println(Thread.currentThread().getName() + " : " + "第" + (tickets--) + "张票售出");
//
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
// 方法二 使用同步方法达到线程安全
saleTickets();
}
}
// 同步方法:同步的是当前对象
private synchronized void saleTickets() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " : " + "第" + (tickets--) + "张票售出");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 方法三:使用互斥锁ReetrantLock
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: Fuwei Feng
* @version: 2020/4/22
*/
public class SyncThreadDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable {
private int tickets = 10;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// 方法一使用同步代码块
// if(tickets > 0) {
// synchronized (this) {
// System.out.println(Thread.currentThread().getName() + " : " + "第" + (tickets--) + "张票售出");
//
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
// 方法二 使用同步方法达到线程安全
// saleTickets();
// 方法三 使用互斥锁
saleTickets2();
}
}
// 同步方法:同步的是当前对象
private synchronized void saleTickets() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " : " + "第" + (tickets--) + "张票售出");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ReentrantLock lock = new ReentrantLock();
private void saleTickets2() {
lock.lock();
try {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " : " + "第" + (tickets--) + "张票售出");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
11. 说一下堆和栈
- java中的堆和栈
java 内存分配详解
寄存器:在程序中无法控制
堆:存放用new 产生的数据
栈:存放基本类型的数据和对象的引用,但是对象本身不存放在栈中,而是存放在堆中。
静态域:存放在对象中用static定义的静态成员。
常量池:存放常量。
内存分配中的栈和堆
-
栈
在函数中定义的一些基本类型(int long char float double byte short boolean)的变量数据,还有对象的引用变量都在函数的栈内存中分配。当在一段代码中定义一个变量时,java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,java会自动释放掉为该变量分配的内存空间。
栈内存,是java程序的运行区,是在线程创建时创建的。它的生命周期跟随线程的生命周期,线程结束,栈也随之释放,对于栈来说不存在垃圾回收问题,只要线程结束了该栈就结束了。
栈中的数据是以栈桢的形式存在的。栈帧是一个内存区块,是一个数据集,是有关方法和运行期数据的数据集。当一个方法A被调用就会产生一个栈帧F1被压入到栈中,A方法再调用B方法,就会产生一个栈帧F2被压入到栈中,执行完毕后,先弹出F2,再弹出F1。 -
堆
堆内存用来存放由关键字new创建的对象和数组。在堆中分配的内存,由JVM自动垃圾收集器来管理。
在堆中创建一个对象后,还可以在栈中定义一个变量,让这个变量的值等于对象在堆内存的首地址,栈中的变量就是对象的引用,相当于java中的指针。当程序运行到对象所在的语句块之外,对象占据的内存不会被自动释放,在没有引用变量指向他的时候,随后一个不确定的时间对象被垃圾收集器回收掉。