1.介绍Java中的装箱和拆箱
- 装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。
- 装箱过程是通过调用包装器的valueOf方法。
- 拆箱过程是通过调用包装器的 xxxValue方法。
- 下面是网上一个比较广泛的例子:
public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
/*
* 在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,
* 便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
* 上面的代码中c和d的数值为3,因此会直接从cache中取已经存在的对象,
* 所以c和d指向的是同一个对象,而e和f则是分别指向不同的对象。
*/
System.out.println(c==d); //true
System.out.println(e==f); //false
/*
* 当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,
* 而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。
* 另外,对于包装器类型,equals方法并不会进行类型转换。
*/
System.out.println(c==(a+b)); //true
System.out.println(c.equals(a+b)); //true
System.out.println(g==(a+b));
/*
* 如果数值是int类型的,装箱过程调用的是Integer.valueOf;
* 如果是long类型的,装箱调用的Long.valueOf方法
*/
System.out.println(g.equals(a+b)); //false
System.out.println(g.equals(a+h)); //true
}
}
2.本地搭建mysql 使用java jdbc工具类,使用其进行简单的数据库操作
public class JDBCUtils {
public void demo1() {
//获得连接(工具类里会先注册驱动)
Connection conn = null;
//conn连接数据库获得语句执行者psmt
PreparedStatement psmt = null;
//psmt执行后获得结果集rs
ResultSet rs =null;
try {
conn = JDBCUtils.getConnection();
String sql = "select * from category";
psmt = conn.prepareStatement(sql);
rs = psmt.executeQuery();
while(rs.next()) {
System.out.println(rs.getInt("cid")+"---"+rs.getString("cname"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(rs, psmt, conn);
}
}
//添加
@Test
public void demo2() {
Connection conn = null;
PreparedStatement psmt = null;
ResultSet rs =null;
try {
//获得连接
conn = JDBCUtils.getConnection();
//获得语句执行者
String sql = "insert into category values (null,?)";
psmt = conn.prepareStatement(sql);
//设置参数
psmt.setString(1, "bag");
int i = psmt.executeUpdate();
if(i > 0) {
System.out.println("添加成功!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(rs, psmt, conn);
}
}
//修改
@Test
public void demo3() {
Connection conn = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
//获得连接
conn = JDBCUtils.getConnection();
//psmt语句执行者
String sql = "update category set cname = ? where cid = ?";
psmt = conn.prepareStatement(sql);
psmt.setString(1, "shoes");
psmt.setInt(2, 5);
int i = psmt.executeUpdate();
if(i > 0) {
System.out.println("修改成功!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(rs, psmt, conn);
}
}
//删除
@Test
public void demo4() {
Connection conn = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
//获得连接
conn = JDBCUtils.getConnection();
//psmt语句执行者
String sql = "delete from category where cid = ?";
psmt = conn.prepareStatement(sql);
psmt.setInt(1, 5);
int i = psmt.executeUpdate();
if(i > 0) {
System.out.println("删除成功!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(rs, psmt, conn);
}
}
}
3.介绍一下mysql锁机制
1. 共享锁(S锁)/排他锁(X锁)
- 事务拿到某一行记录的共享S锁,才可以读取这一行,并阻止别的事物对其添加X锁
- 事务拿到某一行记录的排它X锁,才可以修改或者删除这一行
- 共享锁的目的是提高读读并发
- 排他锁的目的是为了保证数据的一致性
2. 意向锁
- 意向共享锁:预示事务有意向对表中的某些行加共享S锁
- 意向排他锁:预示着事务有意向对表中的某些行加排他X锁
3. 插入意向锁(insert intention looks)
- 插入意向锁是间隙锁的一种,针对insert操作产生。目的是提高插入并发。
- 多个事物,在同一个索引,同一个范围区间进行插入记录的时候,如果 插入的位置不冲突,不会阻塞彼此。
4. 记录锁
- 对单条索引记录进行加锁,锁住的是索引记录而非记录本身,即使表中没有任何索引,MySQL会自动创建一个隐式的row_id作为聚集索引来进行加锁。
5. 间隙锁(gap锁)
- 封锁记录中的间隔,防止间隔中被其他事务插入。
- 间隙锁主要出现在RR隔离级别,避免出现幻读。
6. 临键锁(Next-Key Locks)
- 临键锁是记录锁和间隙锁的组合,既锁住了记录也锁住了范围。
- 临键锁的主要目的,也是为了避免幻读。
- 如果把事务的隔离级别降级为RC,临键锁就也会失效。
7. 自增长锁: 自增长锁是一种表级锁,专门针对auto_increment类型的列。
4.介绍和实践java多线程,了解多线程生命周期
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程的生命周期:
1. 新建(New): 新创建一个线程对象。
2. 可运行(Runnable): 对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3. 运行(Running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
4. 阻塞(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
5. 死亡(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
多线程的实现:
- 创建线程类:
- 继承Thread类
- 实现Runnable接口:
- 使用Runnable接口可以避免由于JAVA的单继承性带来的局限性
- 适合多个相同的程序代码的线程去处理同一资源情况,把线程同程序的代码、数据有效的分离;
- 有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。
- 通过Thread类构造器来创建线程对象:
- Thread( )
- Thread(Runnable target)
- 通过start()方法激活线程对象
线程状态的控制:
- 线程睡眠——sleep
- 让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread的sleep方法。只有当睡眠的时间结束,才会重新进入到就绪状态。
- 线程让步——yield
- 和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次
- 线程合并——join
- 线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能。
线程同步:
- 使用Synchronized关键字
- 同步方法:
- 给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。
- 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
- 同步代码块:
- 即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步;
- 同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有关系。
- 同步方法:
- 使用特殊域变量(volatile)实现线程同步
- volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
- 在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制;
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
- 使用可重入锁实现线程同步
- 在JDK中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock。
- ReentrantLock和synchronized都是独占锁
- synchronized加锁解锁的过程是隐式的,用户不用手动操作
- ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。
- ReentrantLock和synchronized都是可重入的
- 使用局部变量(ThreadLocal)实现线程同步
- ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。
- ThreadLocal采用了“以空间换时间”的方式,为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
多线程实例(模拟售票)
synchronized关键字修饰普通方法时,获得的锁是对象锁,也就是this。而修饰静态方法时,锁是类锁,也就是类名.class。
public class TicketsTest {
public static void main(String[] args) {
//创建Runnable对象
TicketsSale ts = new TicketsSale();
//创建线程对象
Thread t1 = new Thread(ts);
t1.setName("窗口1");
Thread t2 = new Thread(ts);
t2.setName("窗口2");
Thread t3 = new Thread(ts);
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
public class TicketsSale implements Runnable {
static int tickets = 100;
//创建锁对象
Object obj = new Object();
@Override
public void run() {
while(true) {
synchronized (obj) {
if(tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+tickets--);
}
}
}
}
}
5.理解ConcurrentHashMap 实现原理,最好分别介绍 一下jdk1.8和jdk1.7的ConcurrentHashMap实现原理的区别。
- 众所周知,HashMap是线程不安全的,而HashTable虽然是线程安全的,但是效率低下,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。所以在一些并发操作中CurrenthashMap便派上了用场。
- JDK1.7的实现是Segment+HashEntry+链表。HashMap由数组+链表组成,而CurrentHashMap相当于在数组这个结构又进行细分,分组形成一个个Segment.在HashTable中整个HashTable只有一个锁,而在CurrentHashMap中有很多个Segment,相当于有很多个锁,因此支持并发操作。同一个Segment的并发写入需要上锁,但是不同Segment的写入是可以并发执行的。
- JDK1.8中对CurrentHashMap做了改进,使用是优化的synchronized关键字和 cas操作了维护并发。数据结构的改进主要有两个方面:
- 取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
- 将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。
6.理解JVM内存模型和String对象内存分配
-
JVM内存模型
- 程序计数器:占用很小的一片区域,程序计数器是一个记录着当前线程所执行的字节码的行号指示器。
- Java 虚拟机栈:
- 虚拟机栈生命周期与线程相同。启动一个线程,程序调用函数,栈帧被压入栈中,函数调用结束,相应的是栈帧的出栈。
- 当JVM在执行方法时,会在此区域中创建一个栈帧来存放方法的各种信息,比如返回值,局部变量表和各种对象引用等,方法开始执行前就先创建栈帧入栈,执行完后就出栈。
- 本地方法栈:和虚拟机栈类似,不过区别是专门提供给Native方法用的。
- Java 堆:存放对象实例,所有对象、数组等都是在此分配内存的,在JVM内存中占的比例也是极大的,也是GC垃圾回收的主要阵地。
- 方法区:和java堆一样,方法区是一块所有线程共享的内存区域。方法区主要是放一下类似类定义、常量、编译后的代码、静态变量等。
-
在下面这段程序中:
- 在执行String str1 = "abc"的时候,JVM会首先检查字符串常量池中是否已经存在该字符串对象,如果已经存在,那么就不会再创建了,直接返回该字符串在字符串在字符串常量池中的内存地址;如果该字符串还不存在字符串常量池中,那么就会在字符串常量池中创建该字符串对象,然后再返回。所以在执行String str2 = "abc"的时候,因为字符串常量池中已经存在“abc”字符串对象了,就不会在字符串常量池中再次创建了,所以栈内存中str1和str2的内存地址都是指向"abc"在字符串常量池中的位置,所以str1 = str2的运行结果为true。
- 而在执行String str3 = new String(“abc”)的时候,JVM会首先检查字符串常量池中是否已经存在“abc”字符串,如果已经存在,则不会在字符串常量池中再创建了;如果不存在,则就会在字符串常量池中创建"abc"字符串对象,然后再到堆内存中再创建一份字符串对象,把字符串常量池中的"abc"字符串内容拷贝到内存中的字符串对象中,然后返回堆内存中该字符串的内存地址,即栈内存中存储的地址是堆内存中对象的内存地址。String str4 = new String(“abc”)是在堆内存中又创建了一个对象,所以str 3 == str4运行的结果是false。
public void test1() {
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
/*
* 如果是Stringx="abc";编译器首先会在常量池中寻找有没有"abc"这个字符串,
* 如果有则直接从常量池中取,不会new,
* 如果没有则在常量池中创建一个此字符串对象,
* 然后堆中再创建一个常量池中此“abc”对象的拷贝对象
*/
System.out.println(str1 == str2); //true
System.out.println(str1 == str3); //false
System.out.println(str3 == str4); //false
//equals比较的是值
System.out.println(str1.equals(str2)); //true
System.out.println(str1.equals(str3)); //true
}
7.理解java序列化原理和作用
Java序列化:
- java序列化是指把java对象转换为字节序列的过程,而java反序列化是指把字节序列恢复为java对象的过程
- 通过实现java.io.Serializable接口,可以在Java类中启用可序列化。它是一个标记接口,意味着它不包含任何方法或字段,仅用于标识可序列化的语义。
- Transient 关键字:transient修饰符仅适用于变量,不适用于方法和类。在序列化时,如果我们不想序列化特定变量以满足安全约束,那么我们应该将该变量声明为transient。执行序列化时,JVM会忽略transient变量的原始值并将默认值保存到文件中。因此,transient意味着不要序列化。
- final变量将直接通过值参与序列化
- 静态变量不是对象状态的一部分,因此它不参与序列化。
- serialVersionUID :
- 是表示一个类的序列化标识,和一个类的类名以及包路径一起组成类在序列化时的唯一标识。
- 当两个jvm中存在serialVersionUID+类名+包路径一致的类时,这两个jvm可以通过序列化达到此类数据共享的目的。
- 当设置serialVersionUID为1L时,指代忽略serialVersionUID 的校验。
作用:
- 当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本,图片,音频,视频等,而这些数据都会以二进制的形式在网络上传送。
- 当两个java进行进行通信时,要传送对象,怎么传对象?通过序列化与反序列化。
- 也就是说,发送方需要把对象转换为字节序列,然后在网络上传送,另一方面,接收方需要从字节序列中恢复出java对象。
8.了解javascript/jquery 基本语法和常用选择器
基本选择器
- ID选择器:通过 id 来查找 HTML 元素
//JS
var myElement = document.getElementById("id01");
//JQuery
var myElement = $("#id01");
- 元素选择器:通过标签名来查找 HTML 元素
//JS
var myElements = document.getElementsByTagName("p");
//JQuery
var myElements = $("p");
- 类选择器:通过类名来查找 HTML 元素
//JS
var myElements = document.getElementsByClassName("intro");
//JQuery
var myElements = $(".intro");