Java
思考:学了计算机之后的感悟,计算机是讲逻辑的地方,所有复杂的设计都是简单的逻辑,复杂在于有时候需要一直推理,好似多起疑案混合出现。
面向对象
封装:封装是一种将数据(变量)和作用于数据(方法)的代码打包为一个单元的机制。
继承:继承允许类继承另一个类的一些通用的字段和方法。
多态:多态是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。(多种表现)
集合
类型 | 特点 | 种类 | 特点 |
---|---|---|---|
List | 可重复,插入有序 | ArrayList | 底层数组,查询快,增删慢。线程不安全。 |
List | 可重复,插入有序 | LinkedList | 底层链表,查询慢,增删快。线程不安全。 |
List | 可重复,插入有序 | Vector | 底层数组,查询快,增删慢。线程安全。 |
Queue | 大小排序 | PriorityQueue | 底层数组 |
Set | 不重复,无序 | HashSet | 底层哈希表(数组+链表+红黑树)。线程不安全 |
Set | 不重复,插入有序 | LinkedHashSet | 底层链表+哈希表。线程不安全 |
Set | 不重复,值排序 | TreeSet | 红黑树 |
Map | 无序 | HashMap | 数组+链表+红黑树。线程不安全 |
Map | 插入有序 | LinkedHashMap | 数组+链表+红黑树+双向链表。线程不安全 |
Map | 按key有序 | TreeMap | 红黑树。线程不安全 |
CopyOnWriteArrayList
CopyOnWriteArraySet
ConcurrentHashMap
comparator
1:比较:交换。不比较直接返回:不动。
0:比较:相同覆盖。不比较直接返回:不动。
-1:比较:不动。不比较直接返回:按输入倒叙。
泛型
泛型:把明确类型的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。
希望在编译时被告知错误,而不是在运行时报异常。所以需要提前指示数据类型。
好处:
避免了类型强转的麻烦;
提供了编译期的类型安全;
反射
反射:反向获取类的各种信息。动态获取的信息以及动态调用对象的方法的。
问题
-
HashMap为什么线程不安全?
答:jdk1.8,会出现数据覆盖。当A,B两个线程同时进行put操作,A线程进行hash碰撞判断后挂起,B线程进行hash碰撞判断,并写入数据,此时A线程恢复执行,B线程写入的数据会被A线程的数据覆盖。
-
使用线程安全的CurrentHashMap代替线程不安全的HashMap。
答:jdk1.8,ConcurrentHashMap 的优势在于兼顾性能和线程安全,一个线程进行写操作时,它会锁住一小部分,其他部分的读写不受影响,其他线程访问没上锁的地方不会被阻塞。使用Node+CAS+Synchronized保证线程安全。CAS(compare and swap)
-
volatile、final、static、transient修饰作用?
volatile:保持可见性,多线程环境中,一个线程对共享变量的操作对其他线程是不可见的。synchronized、lock都可保持可见性。
final:修饰后,不可继承和修改。
static:静态,在加载过程中准备阶段被初始化,但是这个阶段只会赋值一个默认的值(0或者null而并非定义变量设置的值)初始化阶段在类构造器中才会赋值为变量定义的值。
transient:不可序列化。 -
并发特点:原子性、可见性、有序性。
-
java类的生命周期:加载-连接(验证,准备,解析)-初始化-使用-卸载。
-
ABA问题。
答:ABA是CAS引起的问题,在T线程对A进行判断的时候,如果其他线程已经将A转换成了B,之后又转换成了A,那么T是没有发现的,会引发问题。可以加版本号解决这个问题。
-
==和equals的区别?
答:==比较的是两个对象的引用(即内存地址)是否相等,而equals ()比较的是两个对象的值(即内存地址里存放的值)是否相等。
锁的理解
synchronized:关键字。
lock:接口。它的实现有(ReentrantLock、WriteLock、ReadLock)
- 设计分类:悲观锁、乐观锁。
- 乐观锁:以循环的CAS(Compare-and-Swap)的方式实现锁。适合读频繁的操作。java.util.concurrent.atomic包里面的原子类都是利用乐观锁实现的。
- 悲观锁:synchronized、lock都是悲观锁。适合写频繁的操作。
- 程度分类:偏向锁,轻量级锁,重量级锁
- 偏向锁:偏向于初恋的锁。不会主动释放偏向锁,如果是初恋,获得所。无竞争和轻微竞争时程度。
- 轻量级锁:有稍微竞争。抢不到锁会自旋等待。
- 重量级锁:有严重竞争。自选太久,挂起,等待唤醒,挂起唤醒都消耗资源。
- 是否如可重入:带Reentrant的都是可重入锁,可以多次进入,递归式地使用。java中的锁都是可重入锁,不可重入锁需要自己实现。
- 种类分类:公平锁,非公平锁。
- 公平锁:FIFO先到先得。
- 非公平锁:随机。
synchronized和lock都有非公平锁和公平锁两种类型。
(思维:可以把问题想象成汽车排队,多个车道想过同一个桥,桥上每次只允许一个车通过。桥的设计(悲观锁和乐观锁),堵塞程度(偏向锁,轻量级锁,重量级锁),通行政策(公平锁和非公平锁)。很少人排队,等待(轻量级锁),很多人排队,睡一会,等叫醒(重量级锁))
AbstractQueuedSynchronizer(抽象队列同步,AQS队列)
它是基于先进先出FIFO实现的等待队列,AQS队列是由Node节点组成的双向链表实现的,如果一个线程获取锁就直接成功,如果失败了就将其放入等待队列当中。
CopyOnWrite(副本改写,COW)
当有人想修改内容时,先复制一个副本,改写后再让原本引用指向副本,完成修改。读写分离。
- 优点:
- 读取性能高;
- 读写分离;
- 缺点:
- 内存占用大;
- 数据不一致(最终一致,不是实时一致)。
线程
创建线程的三种方法:
- 继承Thread:调用start()方法,开始执行run方法块。new Thread()。
- 实现Runnable接口:重写run方法。new Thread(Runnable)。
- 实现Callable接口:实现call()方法。new Thread(new FutureTask(Callable))。
- 优点:继承Thread实现方法简单。实现接口可以再继承其他方法。
- 缺点:继承Thread之后,不能继承其他方法。实现接口编程稍微复杂。
Runnable和Callable的方法区别:
(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
线程池
创建线程池的四种方式:
- newCacheThreadPool:创建一个可缓存的无界线程池,如果线程池长度超过处理需要,可灵活回收,若无可回收,则新建线程。当线程池中的线程空闲时间超过60s,则会自动回收该线程,当任务超过线程池的线程数则创建新的线程,线程池的大小上限为Integer.MAX_VALUE,可看作无限大。
- newFixedThreadPool:创建一个指定大小的线程池,可控制线程的最大并发数,超出的线程会在LinkedBlockingQueue阻塞队列中等待。
- newScheduledThreadPool:创建一个定长的线程池,可以指定线程池核心线程数,支持定时及周期性任务的执行。
- newSingleThreadExecutor:创建一个单线程化的线程池,它只有一个线程来执行任务,保证所有的任务按照指定顺序(FIFO,LIFO,优先级)执行,所有的任务都保存在队列LinkedBlockingQueue中,等待唯一的单线程来执行任务。
强类型语言、弱类型语言、动态语言、静态语言
强类型语言:需要先定义才能使用的语言,对于已经定义好的语言,需要强制转换才能转化数据类型。
弱类型语言:指定变量后,可以看做任意类型的语言。
动态语言:运行时才会检查数据类型。运行时才保存他的数据类型。
静态语言:编译期间检查的数据类型。需要事先指定。
JVM
- 类加载过程
- 类加载器
- 垃圾回收算法
- 垃圾回收器
类加载过程
类加载过程:加载–连接(验证,准备,解析)–初始化–使用–卸载
加载:将class文件的二进制数据转化为运行时方法区的数据结构,在内存中生成该类的对象,作为访问的入口。
连接 :
1. 验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求。
2. 准备:正式为类变量分配内存(被static修饰的变量)并设置类变量初始值为零值的阶段。
3. 解析:虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化:执行类构造器<clinit>()方法。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。
使用:
卸载:
类加载器
启动类加载器
扩展类加载器
应用类加载器
自定义加载器
双亲委派模型:除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
垃圾回收算法
- 标记清除算法:会有大量空间碎片产生。
- 复制算法:只能使用50%的部分。
- 标记压缩算法:
- 分代收集算法:根据对象存活周期的不同将内存划分为几块,新生代使用复制算法,老年代使用标记清除算法、标记压缩算法。
垃圾回收器
垃圾回收器:
serial:单线程,
parNew:多线程
parallel Scavenge:关注吞吐量
serial old:老年代版本,使用标记-压缩
parallel old:老年代版本,使用标记-压缩
CMS:以获取最短回收停顿时间为目标的,并发收集,低停顿
G1:G1是一款面向服务端应用的垃圾收集器,具有大内存、多处理器的机器。
JVM优化
-
编译期:
- 泛型和类型扫除
- 自动装箱、自动拆箱
泛型:避免类型转换和编译期的类型安全。
类型擦除:编译后的字节码文件中,泛型将回到原来的类型,如ArrayList<Stirng> 和 ArrayList<Integer>在编译后的文件中都是ArrayList<E>。
自动装箱:将数据封装为一个数据类型
自动拆箱:从数据类型中拆出数据
- 运行期:
线程安全
锁
线程
内核线程:由操作系统内核支持,操作系统可以同时处理多个事件。
用户线程:建立在用户空间的线程库中,快速低消耗。