面向对象/集合框架/并发

该博客聚焦Java面试知识,涵盖面向对象、集合框架和并发三方面。介绍了String等类区别、面向对象三大特征、接口与抽象类差异;阐述了HashMap等集合原理及区别;讲解了线程安全保证方式、锁机制、线程池原理等并发知识。

一、面向对象

string、stringbuffer、stringbuilder之间的区别
String的值是不可变的,每次操作都会生成大量对象,浪费空间
StringBuffer对象的值是可变的,是线程安全的,可以在多线程下运行
StringBuilder值是可变的,不能在多线程下运行,运行速度快

面向的三大特征
(1)封装 把属性设为private,隐藏内部细节限制外部直接访问,提供公共的get和set方法间接访问,提升安全性。
(2)继承 子类继承父类,所有非私有的属性和方法(不包含构造器和主方法)Java只能单一继承(可以通过多重继承来实现多继承)
(3)多态 就是同一个接口,使用不同的实例而执行不同操作
要有继承或实现关系,要有方法的重写,要有父类引用指向子类对象
成员变量编译看父类,运行看父类;成员方法编译看父类,运行看子类

接口和抽象类都是用来定义对象的公共行为的,不同点:
(1)一个类只能继承一个抽象类,而一个类却可以实现多个接口
(2)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法
(3)抽象类可以提供成员方法的实现细节,而接口中的方法不可以
(4)接口的方法默认是public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法
(5)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的
(6)接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象, 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

静态内部类与非静态内部类的区别
(1)非静态内部类能够访问外部类的静态和非静态成员,静态类只能访问外部类的静态成员。
(2)非静态内部类不能脱离外部类被创建,静态内部类可以。

Java中在传参数时是将值进行传递,还是传递引用
值传递:在方法调用时,传递的参数是这个参数指向值的拷贝;
引用传递:在方法调用时,传递引用的地址

String s = new String(“xxx”);创建了几个String对象?
如果xxx这个字符串常量不存在,则创建两个String对象;而如果存在则只会创建一个String对象。

finally中的代码一定会执行吗?try里有return,finally还执行么
在正常情况下,finally中的代码一定会得到执行,但是如果我们将执行try-catch-finally 代码块的线程设置为守护线程,或者在fianlly之前调用System.exit结束当前虚拟机,那么finally则不会得到执行
当try和finally里都有return时,会忽略try的return,而使用finally的return。

基本数据类型多少字节
byte:1(字节)    
short:2(字节) 
int:4(字节) 
long:8(字节) 
float:4(字节) 
double:8(字节) 
boolean:1(字节) 
char:2(字节)

深拷贝和浅拷贝区别
浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

TCP和UDP有什么区别
udp是无连接的;tcp是面向连接的
udp是不可靠传输;tcp是可靠传输;
udp是面向报文传输;tcp是面向字节流传输。
upd支持一对一,一对多,多对多,多对一交互通信;tcp只能是一对一通信

二、集合框架

hashmap的底层原理,和hashtable的区别
put():对key的hashCode做hash操作,然后再计算在bucket中的index;如果没碰撞直接放到bucket里; 如果碰撞了,以链表的形式存在buckets后; 如果节点已经存在就替换old value(保证key的唯一性)
如果bucket满了就要扩容。
get():通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表中查找对应的节点。
它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
jdk1.8及以上版本引入了红黑树,当链表的长度大于或等于8的时候则会把链表变成红黑树,以提高查询效率
扩容的问题:
负载因子越大,导致哈希冲突的概率也就越大,负载因子越小,浪费的空间也就越大(负载因子0.75,出始容量16)
(1)当多个线程同时发现hashMap需要调整大小,容易导致条件竞争,进而死锁。
(2)rehash操作是耗时操作。
HashMap和Hashtable的区别
(1)HashMap是线程不安全的,HashTable是线程安全的
(2)HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行
(3)单线程环境下Hashtable比HashMap效率高

并发集合有哪些?
ConcurrentHashMap:线程安全的HashMap的实现
CopyOnWriteArrayList:线程安全且在读操作时无锁的ArrayList
CopyOnWriteArraySet:基于CopyOnWriteArrayList,不添加重复元素
ArrayBlockingQueue:基于数组、先进先出、线程安全
LinkedBlockingQueue:基于链表实现,读写各用一把锁,在高并发读写操作都多的情况下,性能优于ArrayBlockingQueue

ConcurrentHashMap
HashMap在多线程环境下存在线程安全问题,一般在多线程的场景,我都会使用好几种不同的方式去代替:
(1)使用Collections.synchronizedMap(Map)创建线程安全的map集合;
(2)Hashtable
(3)ConcurrentHashMap
出于线程并发度的原因,我都会舍弃前两者使用最后的ConcurrentHashMap,他的性能和效率明显高于前两者。
Collections.synchronizedMap内部维护了一个普通对象Map,还有排斥锁mutex,创建出synchronizedMap之后,再操作map的时候,就会对方法上锁
Hashtable在对数据操作的时候都会上锁,所以效率比较低下,Hashtable在我们put 空值的时候会直接抛空指针异常,但是HashMap却做了特殊处理
如果键使用null值,就会使得其无法判断对应的key是不存在还是为空,因为你无法再调用一次contain(key)来对key是否存在进行判断,ConcurrentHashMap同理。

ConcurrentHashMap
jdk1.7底层是基于 数组 + 链表 组成的,put方法采用了分段锁技术,其中Segment继承于ReentrantLock。由于HashEntry中的 value 属性是用 volatile 关键词修饰的
get方法是非常高效的,因为整个过程都不需要加锁。
数组加链表的方式,我们去查询的时候,还得遍历链表,会导致效率很低,这个跟jdk1.7的HashMap是存在的一样问题,所以他在jdk1.8完全优化了。
jdk1.8底层是基于 数组 + 链表 +红黑树,抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。
CAS 是乐观锁的一种实现方式,是一种轻量级锁,CAS 操作的流程是线程在读取数据时不进行加锁,在准备写回数据时,比较原值是否修改,
若未被其他线程修改则写回,若已被修改,则重新执行读取流程。这是一种乐观策略,认为并发操作并不总会发生。
CAS很经典的ABA问题,就是说来了一个线程把值改回了B,又来了一个线程把值又改回了A,对于这个时候判断的线程,就发现他的值还是A,
所以他就不知道这个值到底有没有被人改过,其实很多场景如果只追求最后结果正确,这是没关系的。用版本号去保证就好了,就比如说,
我在修改前去查询他原来的值的时候再带一个版本号,每次判断就连值和版本号一起判断,判断成功就给版本号加1(AtomicStampedReference就是用版本号实现cas机制)。
比如时间戳也可以,查询的时候把时间戳一起查出来,对的上才修改并且更新值的时候一起修改更新时间,这样也能保证
synchronized 获取锁的方式,JVM 使用了锁升级的优化方式,就是先使用偏向锁优先同一线程然后再次获取锁,如果失败,就升级为 CAS 轻量级锁,
如果失败就会短暂自旋,防止线程被系统挂起。最后如果以上都失败就升级为重量级锁。

三、并发

如何保证线程安全?有哪些方式
(1)synchronized关键字:使用synchronized关键字可以对共享资源进行同步,使得在同一时间只有一个线程可以访问该资源。synchronized可以修饰方法或代码块,保证了线程的互斥访问和可见性。
(2)Lock接口:Lock是Java5中引入的一个新的同步机制,通过Lock和Unlock方法来对共享资源进行加锁和释放操作,与synchronized相比,可以更加灵活地控制同步范围。
(3)cas乐观锁(AtomicInteger、AtomicLong、AtomicBoolean)它可以保证多个线程同时对一个整数变量进行加减操作时的原子性。
(4)ThreadLocal实现多个线程之间的数据隔离

syschronized 与 Lock区别?
(1)syschronized是一个关键字而Lock是一个接口
(2)Synchronized 使用过后,会自动释放锁,而Lock需要手动上锁、手动释放锁。(
(3)Lock提供了更多的实现方法,而且 可响应中断、可定时, 而synchronized 关键字不能响应中断;
(4)synchronized关键字是非公平锁,Lock的子类ReentrantLock默认是非公平锁,但是可通过一个布尔参数的构造方法实例化出一个公平锁;(公平锁就是:先等待的线程,先获得锁。)
(5)synchronized无法判断,是否已经获取到锁,而Lock通过tryLock()方法可以判断,是否已获取到锁;
(6)Lock可以通过分别定义读写锁提高多个线程读操作的效率。
(7)二者的底层实现不一样:synchronized是同步阻塞,采用的是悲观并发策略;Lock是同步非阻塞,采用的是乐观并发策略(底层基于volatile关键字和CAS算法实现)

voliate关键字
voliate关键字轻量级的同步机制,作用主要有:保证内容可见性、禁止指令重复排序、不保证原子性,一个线程修改变量后,对其他线程可见,用于多个线程读一个线程写的场景。

(1)原子性:原子是构成物质的基本单位,所以原子的意思代表着—“不可分”。由不可分可知,具有原子性的操作也就是拒绝线程调度器中断。
(2)可见性:一个线程对共享变量的修改,另一个线程能够立刻看到,称为可见性。
(3)有序性:程序按照代码的先后顺序执行。编译器为了优化性能,有时会改变程序中语句的顺序,但是不会影响最终的结果。有序性经典的例子就是利用DCL双重检查创建单例对象。

volatile和synchronize有什么区别
(1)volatile 只能作用于变量,synchronized 可以作用于变量、方法、对象。
(2)volatile 只保证了可见性和有序性,无法保证原子性,synchronized 可以保证线程间的有序性、原子性和可见性。
(3)volatile 线程不阻塞,synchronized 线程阻塞。
(4)volatile 本质是告诉 jvm 当前变量在寄存器中的值是不安全的需要从内存中读取;sychronized 则是锁定当前变量,只有当前线程可以访问到该变量其他线程被阻塞。

ReenTrantLock(可重入锁) Synchronized(也可以重入)
(1)ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
(2)ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
(3)ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
(4)ReenTrantLock底层通过AQS实现,Synchronized通过JVM实现
(5)ReenTrantLock需要手动释放,Synchronized自动释放
在这里插入图片描述

重入锁
可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
synchronized 和 ReentrantLock 都是可重入锁。

死锁
1.多个操作者(m >= 2)争夺多个资源(n>=2),n<=m
2.争夺资源的顺序不对
3.拿到资源不放手

互斥使用,即当资源被一个线程占用时,别的线程不能使用
不可抢占,资源请求者不能强制从资源占有者手中抢夺资源,资源只能由占有者主动释放
请求和保持,当资源请求者在请求其他资源的同时保持对原因资源的占有
循环等待,多个线程存在环路的锁依赖关系而永远等待下去,例如T1占有T2的资源,T2占有T3的资源,T3占有T1的资源,这种情况可能会形成一个等待环路

破坏互斥条件:这个有点难,因为资源本身的特性决定了它是否互斥。比如打印机,就是天生的互斥资源。但咱们可以通过资源复用或者资源池来减少互斥的影响。
破坏占有和等待条件:要做到这点,可以让进程在开始执行前一次性申请所有需要的资源。这样就不会在占有一部分资源的情况下等待其他资源了。
破坏不可剥夺条件:当一个已经持有资源的进程请求新资源并且不能立即得到时,它必须释放已占有的资源。这样,其他进程就可以使用这些资源,从而避免了死锁。
破坏循环等待条件:为系统中的资源定义一个线性的顺序,然后规定每个进程按顺序申请资源。这样就可以避免循环等待的发生。

并发和并行的区别和理解
相同点:
并发和并行的目标都是最大化CPU的使用率
不同点:
(1)并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在
(2)并行要求程序能够同时执行多个操作,而并发只是要求程序“看着像是”同时执行多个操作,其实是交替执行。

线程的生命周期
新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五种状态
在这里插入图片描述

wait()和notify()方法的使用,notify 关键字
线程等待wait和通知notify/notifyAll,主要用于多线程之间的通信协作,而且这两个方法都是属于Object类,任何对象都可以调用这两个方法。
当在一个实例对象上调用了wait()方法之后,当前线程就会释放掉它获取到的锁资源,将锁资源让给其他线程去竞争,然后当前线程会被阻塞挂起,
直到另外的线程调用了该对象的notify()方法,处于等待状态的线程才得以继续进行,notify 会随机的唤醒被阻塞到该共享对象上的一个线程,
而 notifyAll() 则会唤醒所有在该共享对象上被wait 方法阻塞而陷入等待状态的线程。
wait() 和 notify()必须配合synchrozied关键字使用,wait()会释放锁,而notify()不释放锁。
sleep会使当前线程睡眠指定时间,不释放锁
yield会使当前线程重回到可执行状态,等待cpu的调度,不释放锁
某线程.join()时会使当前线程等待某线程执行完毕再结束.释放锁

线程池
CPU密集型: 核心线程数 = CPU核数 + 1;
IO密集型: 核心线程数 = CPU核数 * 2;

好处:
重用存在的线程,减少对象创建、消亡的开销,提高性能。
可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
提供定时执行、定期执行、单线程、并发数控制等功能。

原理:
当一个任务通过execute(Runnable)方法添加到线程池时:
Ø 线程数量小于corePoolSize,新建线程(核心)来处理被添加的任务;
Ø 线程数量大于等于 corePoolSize,存在空闲线程,使用空闲线程执行新任务;
Ø 线程数量大于等于 corePoolSize,不存在空闲线程,新任务被添加到等待队列;如果添加成功则等待空闲线程,如果添加失败:
线程数量小于maximumPoolSize,新建线程执行新任务;
线程数量等于maximumPoolSize,拒绝此任务。
Ø 当线程数大于核心线程数事,超过KeepAliveTime(闲置时间),线程会被回收,最终会保持corePoolSize个线程。

七大参数
1.corePoolSize:线程池中的常驻核心线程数
2.maxinumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于一
3.keepAliveTime:多余的空闲线程的存活时间。
当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。
4.unit:keepAliveTime的单位
5.workQueue:任务队列,被提交但是尚未被执行的任务。
6.threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。
7.handler:拒绝策略,表示当队列满了并且工作线程-大于等于线程池的数量最大线程数

拒绝策略:
1.直接抛出异常
2.直接默默丢失
3.策略丢弃任务队列中等待事件最长的
4.谁提交任务谁来执行这个任务

newCachedThreadPool :缓存线程池,如果线程池长度超过处理需要,可回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool : 定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool : 计划线程池,支持定时及周期性任务执行。
newSingleThreadExecutor :单线程线程池,用唯一的线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

假如只有一个cpu,单核,多线程还有用吗
CPU的执行速度要远大于IO的过程,因此在大多数情况下增加一些复杂的CPU计算都比增加一次IO要快。单核CPU可以通过给每个线程分配CPU时间片(时间单元)来实现多线程机制。由于CPU频率很高,故时间单元非常短。所以单核也可以实现多线程机制。

线程跟进程的区别?
1、定义不一样,进程是执行中的一段程序,而一个进程中执行中的每个任务即为一个线程。
2、一个线程只可以属于一个进程,但一个进程能包含多个线程。
3、线程无地址空间,它包括在进程的地址空间里。
4、线程的开销或代价比进程的小。

sychronied修饰普通方法和静态方法的区别
(1)实例方法,作用于当前实例加锁,进入方法前需要获取当前实例的锁;
(2)静态方法,作用于当前类对象加锁,进入方法前需要获取当前类对象的锁;
sychronied修饰普通方法和静态方法,其实也等价于synchronized(this){}与synchronized(class){}。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值