1. 数组(Array) 和列表(ArrayList) 有什么区别?
-
Array:
- 可以存储基本数据类型、引用类型;
- 空间大小固定,且需要提前知道大小以开辟内存空间。
-
ArrayList:
- 只能存储引用类型;
- 是容器框架中的一员,底层是一个数组,可以看作是对数组的二次封装实现;
- 由动态扩容机制(原理是数组的复制),不需要提前确定空间大小。
2.ArrayList 和 Vector 的区别?
- ArrayList
- 是异步的,线程不安全,性能比较高;
- 以1.5倍进行数组的动态扩容,不可指定容量的增量;
- 支持Iterator、ListIterator,不支持Enumeration;
- Vector(ArrayList的早期实现)
- 是同步的,线程安全,性能低;
- 可以指定大小扩容,不指定就是翻一倍;
- 除了支持Iterator、ListIterator,还支持Enumeration。
3. HashMap,TreeMap,HashTable 的区别?
-
HashMap和TreeMap的区别
- 构造方式不同:HashMap在JDK1.8之前,哈希表底层采用数组+链表实现,而JDK1.8中,哈希表存储采用数组+链表+红黑树实现;TreeMap基于红黑树实现的。
- 是否顺序:HashMap是无序的key-value集合,TreeMap是有序的key-value集合。
- 效率:HashMap通常比TreeMap快一点(树和哈希表的数据结构使然)。
-
HashMap和HashTable(HashMap的早期实现) 的区别
- 构造方式:HashMap在JDK1.8之前,哈希表底层采用数组+链表实现,而JDK1.8中,哈希表存储采用数组+链表+红黑树实现;HashTable底层是数组+链表;
- 默认初始容量和扩容方式不同:HashMap的默认初始容量是16,对象数据扩容每次翻一倍,插入后判断扩容;Hashtable默认初始size为11,也是翻一倍,但是是先扩容再插入。
- 线程安全性:HashMap是非synchronized,不同步,线程不安全;Hashtable是synchronized,同步,线程安全。
- 是否可以存null:HashMap允许键和值设置成null;而Hashtable不行。
- 迭代器不同:HashMap的迭代器(Iterator)是快速失败的,而Hashtable的enumerator迭代器不是安全失败的。
- 效率:由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
4. HashMap 的工作原理是什么?
-
插入元素
- 判断数组是否为空或者长度是否为0;
- 如果是,桶扩容(默认初始16);
- 如果不是,继续第二步。
- 执行hashcode方法,根据对象的hashcode计算插入的数组索引;
- 判断数组中这个索引位置是否为空,或是否equals;
- 如果为空,直接插入;
- 如果不为空,相同,就不再插入了,覆盖值;
- 如果不为空且不相同,执行第4步;
- 判断这个节点是否是树节点;
- 如果不是,执行第5步;
- 如果是,红黑树插入(如果发现equals了,就不插入)。
- 尾插法遍历链表,与每个节点比较;
- 如果发现相同,不插入了,覆盖;
- 如果没有发现相同,继续第6步;
- 判断链表长度是否小于8,同时数组长度为64;
- 如果链表长度小于8,链表插入;
- 如果链表长度不小于8,但是数组长度没到达64,触发数组扩容;
- 如果链表长度不小于8,并且数组长度到达了64,转换成红黑树再插入,继续第7步;
- 判断容量是否够(扩容因子0.75)
- 如果够,就结束;
- 如果不够,就数组扩容。
获取元素和删除元素和上述过程类似,获取就是去hashcode找,然后equals比较;不过要注意的是删除的时候,如果原来是红黑树的,删除后红黑树的节点树到了6,那么它就会转换成链表了。
- 判断数组是否为空或者长度是否为0;
5. 什么是序列化,如何实现序列化?
Java 提供了一种对象序列化的机制,即用一个字节序列可以表示一个对象,该字节序列包含该对象的数据 、 对象的类型和对象中存储的属性等信息。序列化就是将对象转换成字节写出到文件之后,相当于文件中持久保存了一个对象的信息。 反之,反序列化就是将该字节序列从文件中读取回来,重构对象。
-
序列化过程:
- 需要序列化的对象所属的类实现Serializable接口或者Exterialzable接口;
- 创建一个ObjectOutputSteam对象,并给入一个字节输出流;
- 调用这个对象的writeObject方法写入到目的地。
-
反序列化过程
- 创建一个ObjectInputSteam对象,并给入一个字节输入流;
- 调用这个对象的readObject方法;
- 如果能找到一个对象的class文件从绑定的写入地写入。
6. 进程和线程有什么区别?
-
进程
是指一个内存中运行的应用程序(现在很多软件都是多进程的了,比如百度云),是系统运行程序的基本单位,每个进程都有一个独立(进程之间的内存不共享)的内存空间 。系统运行一个程序即是一个进程从创建、运行到消亡的过程。
-
线程
是进程中的一个执行路径,线程是进程中的一个执行单元,共享一个内存空间(每个线程都有自己的栈空间,堆内存是共有的),线程之间可以自由切换,并发执行。一个进程最少有一个线程,一个进程如果没有线程在执行了的话就运行结束了,线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
7. java 当中如何实现线程呢?
java中创建线程有三种方法:
- 通过Thread类
- 创建一个类,这个类需要继承Thread类(可以匿名内部类),并重写run()方法;
- new这个类的对象;
- 然后调用这个对象的start方法启动线程。
- 通过Runnable接口
- 创建一个类,这个类实现Runnable接口(可以匿名内部类),并实现run()方法;
- new这个类的对象(这个对象相当于任务);
- new一个Thread类的线程对象,并传入任务;
- 然后调用这个线程对象的start方法启动线程。
- 通过Callable接口
- 编写类实现Callable接口 (可以匿名内部类), 实现call方法;
- 创建FutureTask对象(这个对象相当于任务) , 并传入第一步编写的Callable类对象;
- new一个Thread类的线程对象,并传入任务;
- 然后调用这个线程对象的start方法启动线程。
8.说说线程的生命周期
Thread.State类里面定义了线程的六种状态,这是一个枚举Enum类。
-
NEW(新建状态)
至今尚未启动的线程处于这种状态。
-
RUNNABLE(运行状态)
正在JVM中执行的线程处于这种状态。
-
BLOCKED(阻塞状态)
受阻塞并等待某个监视器锁的线程处于这种状态。
-
WAITING(等待状态)
无限期等待另一个线程来执行某一特定操作的线程处于这种状态。
-
TIMED WAITING(休眠状态)
等另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
-
TERMINATED(死亡状态)
已退出的线程处于这种状态。
- 当我们new一个线程的时候,这个线程就处于NEW状态了;
- 当我们调用这个线程的start方法启动它的时候,这个线程处于RUNNABLE状态;
- 之后可能由三种情况:
- 多个线程抢夺CPU,这个线程没抢到就进入了BLOCKED状态,此时它还是具有cpu的执行资格,等待cpu空闲执行;
- 被sleep(long)或者是wait(long)了,这个线程进入了TIMED_WAITING状态,这个时候它没有cpu执行资格了;
- 被wait()了,没有给等待时间,进入了WAITING状态,无限等待;
- 针对第3步将出现三种情况:
- 原来是BLOCKED状态,抢到了cpu,就执行了,进入RUNNABLE状态;
- 原来是TIMED_WAITING状态,这个线程醒了之后(计时时间到或者被提前唤醒notify):
- 如果cpu空闲,进入RUNNABLE状态;
- 如果cpu不空虚,进入BLOCKED状态;
- 原来是WAITING状态,被notify唤醒了之后:
- 如果cpu空闲,进入RUNNABLE状态;
- 如果cpu不空虚,进入BLOCKED状态;
- 当这个线程执行完毕或者被中断了,就进入TERMINATED死亡状态了。
9. 多线程并发或线程安全问题如何解决?
- synchronized(同步代码块或者同步方法),实现线程同步,由JVM统一管理(悲观锁);
- 通过volatile 关键字修饰变量,可以实现线程之间的可见性,避免变量脏读的出现,底层是通过限制jvm指令的重排序来实现的;
- 通过Lock锁(底层CAS),对可能出现线程安全问题的共享资源,进行手动加锁、解锁;
- 通过线程安全的集合类,ConcurrentHashMap、CopyonWriteArrayList;
- 通过JUC包下的类去设计。
10. synchronized 和 ReentrantLock 的区别
-
(一)出身不同
- Synchronized
- Java中的关键字,是由JVM来维护的。是JVM层面的锁。
- 底层是通过monitorenter进行加锁(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。只有在同步块或者是同步方法中才可以调用wait/notify等方法的。因为只有在同步块或者是同步方法中,JVM才会调用monitory对象的);通过monitorexit来退出锁的。
- Lock
- 是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁。使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- Lock是通过调用对应的API方法来获取锁和释放锁的。
- Synchronized
-
(二)使用方式不同
-
synchronized
-
有代码块锁和方法锁,程序能够自动获取锁和释放锁。其是由系统维护的,除非逻辑问题,不然不会出现死锁,死锁的四个必要条件(破坏其一):
-
互斥条件
一个资源每次只能被一个进程使用;
-
请求与保持条件
一个进程因请求资源而阻塞时,对已获得的资源保持不放;
-
不剥夺条件
进程已获得的资源,在末使用完之前,不能强行剥夺;
-
循环等待条件
若干进程之间形成一种头尾相接的循环等待资源关系。
-
-
原始采用的是CPU悲观锁,即独占锁。当很多线程竞争锁的时候,会引起CPU频繁的上下文切换,效率低。
-
-
Lock
- Lock只有代码块锁,需要手动的获取和释放锁,并且需要配合try/finaly语句块来完成(保证资源释放,不被死锁)。如果没有释放锁,就有可能导致出现死锁的现象。
- 采用得到是乐观锁的方式(Compare and Swap, CAS),每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
-
-
(三)等待是否可以被中断
- synchronized
不可中断,除非抛出异常或者正常运行完成。 - Lock
可以中断的,中断方式:- 调用设置超时方法tryLock(long timeout ,timeUnit unit)
- 调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
- synchronized
-
(四)是否可以设置成公平锁
-
synchronized
只能为非公平锁。 -
lock
两者都可以的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。
true:公平锁、false:非公平锁
-
-
(五)锁绑定多个条件condition
- synchronized
不能精确唤醒线程。要么随机唤醒一个线程;要么是唤醒所有等待的线程。 - Lock
用来实现分组唤醒需要唤醒的线程,可以精确的唤醒。
- synchronized
-
(六)性能区别
- synchronized
- 托管给JVM执行,Java1.5中,由于需要调用操作接口,可能导致加锁消耗时间过长,与Lock比性能低。
- 1.6以后,语义定义更加清晰,有适应自旋、锁粗化、锁消除、轻量级锁、偏向锁等,可进行许多优化,性能提高了,与Lock差不多。
- Lock
java写的控制锁的代码,性能高。
- synchronized