1.说一下线程和进程的区别?
进程是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念。线程是进程的一个执行单元,是进程内调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。一个程序至少一个进程,一个进程至少一个线程,多个线程可以共享数据。
狭义上来说,进程是指正在运行的程序,打开任务管理器即可看到进程对应的一推软件。通俗地说,进程是应用程序在内存中分配的存储空间。
线程是进程中负责负责程序执行、运算的执行单元,类似于工厂里的工人,工人把工厂按照一定的规则继续运行下去。
一个进程至少有一个线程来负责程序的进行,有多个线程来运行程序的时候就叫多线程。当我们启动一个Java程序的时候,操作系统就会创立一个Java进程,这个Java进程可以创建多个线程(执行main方法的就是一个名为main的线程),这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时进行。
1.简单而言,一个应用程序就是一个进程,而线程是一个进程内部的多个运行单位。
2.多个进程的内部数据和状态都是完全独立存在的,而多线程是共享一块内存空间和一组系统资源(同一进程内),在程序内部可以互相调用(通过对象方法);而进程间通信大多数情况是必须通过网络实现的
3.线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程的切换负担要小。
4.进程是资源分配的基本单位,线程是程序运行,cpu调度的基本单位。
2.Java中实现多线程的方式?
Java多线程实现共有以下几种方式。
1.继承Thread类,重写run方法,创建Thread对象调用start()方法启动线程。
2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target。
3.通过Callable和FutureTask创建线程。
4.通过线程池创建线程。
前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果。后面两种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中。
3.LinkedList和ArrayList的区别?
ArrayList和Vector使用了数组的实现,可以认为ArrayList或者Vector封装了对内部数组的操作,比如向数组中添加,删除,插入新的元素或者数据的扩展和重定向。LinkedList使用了循环双向链表数据结构。
ArrayList底层是数组,查询快、增删慢;LinkedList底层是链表,查询慢、增删快。
4.Java的集合有哪些?
Map接口和Collection接口是所有集合框架的父接口
Collection接口的子接口包括:Set接口和List接口。Set中不能包含重复的元素。List是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。
Map接口的实现类主要有:HashMap、Hashtable、ConcurrentHashMap以及TreeMap等。Map不能包含重复的key,但是可以包含相同的value。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
5.HashMap底层是什么数据结构?
JDK1.7及以前:数组+链表;JDK1.8:数组+链表+红黑树
当添加元素时,会通过哈希值和数组长度计算计算下标来准确定位该元素应该put的位置,通常我们为了使元素时分布均匀会使用取模运算,用一个值去模上总长度,计算出index后,就会将该元素添加进去,理想状态下是将每个值都均匀的添加到数组中,但问题是不可能达到这样的理想状态,这时候就会产生哈希冲突。即产生了相同的索引。此时,就产生了第二种数据结构——链表,冲突的元素会在该索引处以链表的形式保存。不过当链表长度过长时,其固有弊端就明显出来了,即查询效率较低,时间复杂度会达到O(n)级别,而数组的查询时间复杂度仅为O(1)。此时,就引出了第三种数据结构——红黑树,红黑树是一棵接近于平衡的二叉树,其查询时间复杂度为O(logn),远远比链表的查询效率高。但是红黑树的自身维护的代价也是比较高的,每插入一个元素都可能打破红黑树的平衡性,这就需要每时每刻对红黑树再平衡(左旋、右旋、重新着色)。
为什么数组的长度必须是2的指数次幂?
添加元素时索引的下标可以通过取模运算获得,但是我们知道计算机的运行效率:加法(减法)>乘法>除法>取模,取模的效率是最低的。所以我们要在HashMap中避免频繁的取模运算,又因为在我们HashMap中他要通过取模去定位我们的索引,并且HashMap是在不停的扩容,数组一旦达到容量的阈值的时候就需要对数组进行扩容。那么扩容就意味着要进行数组的移动,数组一旦移动,每移动一次就要重回记算索引,这个过程中牵扯大量元素的迁移,就会大大影响效率。那么如果说我们直接使用与运算,这个效率是远远高于取模运算的。首先使用位运算来加快计算的效率,而要使用位运算,就需要数组-1然后与hash值保证其在数组范围内,只有当数组长度为2的指数次幂时,其计算得出的值才能和取模算法的值相等,并且保证能取到数组的每一位,减少哈希碰撞,不浪费大量的数组资源。
算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1),hash%length==hash&(length-1)的前提是length是2的n次方
为什么HashMap的加载因子要设置为0.75?
加载因子如果定的太大,比如1,这就意味着数组的每个空位都需要填满,即达到理想状态,不产生链表,但实际是不可能达到这种理想状态,如果一直等数组填满才扩容,虽然达到了最大的数组空间利用率,但会产生大量的哈希碰撞,同时产生更多的链表,显然不符合我们的需求。
但如果设置的过小,比如0.5,这样一来保证了数组空间很充足,减少了哈希碰撞,这种情况下查询效率很高,但消耗了大量空间。
因此,我们就需要在时间和空间上做一个折中,选择最合适的负载因子以保证最优化,取到了0.75
为什么链表长度大于等于8时转成了红黑树?
这里要提到一个概率论中的泊松分布,因为链表长度大于等于8时转成红黑树正是遵循泊松分布。
HashMap节点分布遵循泊松分布,按照泊松分布的计算公式计算出了链表中元素个数和概率的对照表,可以看到链表中元素个数为8时的概率已经非常小。
另一方面红黑树平均查找长度是log(n),长度为8的时候,平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,这才有转换为树的必要。链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是链表和红黑树之间的转换也很耗时。