文章目录
- 多线程
- 1. 进程和线程的关系
- 2. 创建线程的几种方式
- 3. 继承Thread类和实现Runnable两种方式的区别
- 4. 启动线程使用start还是run
- 5. 多线程的执行是什么顺序
- 6. sleep和wait区别
- 7. 线程的生命周期
- 8. 什么是公平锁和非公平锁?
- 9. synchronized和同步锁的区别?
- 10. 线程同步的三大特征:
- 11. synchronized与volatile的区别?
- 12. 悲观锁和乐观锁?
- 13. 线程的等待和通知机制如何实现
- 14. 什么是生产者消费者模式,解决了什么问题,实现的思路是什么?
- 15. 阻塞队列是什么,有什么作用?
- 16. ThreadLocal是什么,有什么作用?
- 17. 讲讲强软弱虚引用
- 线程池
- 网络通信
多线程
1. 进程和线程的关系
- 进程 是运行起来的程序,是操作系统分配资源cpu、内存的最小单元,
- 线程 是CPU分配和调度的最小单元,一个进程可以由一个或多个线程组成。
- 区别:
- 内存方面:
进程需要的资源更多(内存(堆、方法区、本地方法区)),线程更轻量级(栈、程序计数器)- 创建和销毁:
进程需要更多的时间和资源,线程更快。- 通信方面:
进程之间通讯比较麻烦(RPC、网络),线程之间通讯更容易(通过线程共享的内存空间)
2. 创建线程的几种方式
- 继承Thread类,重写run方法。
- 实现Runnable接口,实现run方法;
- 实现Callable接口,实现Call方法,创建一个FutureTask对象;
3. 继承Thread类和实现Runnable两种方式的区别
- java支持单继承多实现,继承Thread类之后无法继承其他类,实现接口无限制;
- 继承Thread类可以不用重写run方法,实现接口必须重写run方法;
- 实现Runnable接口可以使用lambda表达式,语法更加简洁。
4. 启动线程使用start还是run
- 使用run方法是在主线程中执行,使用start方法才是开启并执行线程。
5. 多线程的执行是什么顺序
多线程的执行是抢占式的,线程会去抢占cpu,抢到后执行自己的指令,执行过程中可能会被其他线程抢占,当切换回原来的线程时,线程通过自己的程序计数器来继续执行代码。
6. sleep和wait区别
- 调用对象不同,wait是锁对象调用,sleep是当前线程调用。
- 唤醒机制不同,线程进入wait后,要通过锁对象notify/notifyAll唤醒,sleep当时间结束之后自动唤醒。
- 锁释放不同,线程进入wait后,会自动释放锁,线程sleep不会释放锁。
7. 线程的生命周期
- new新建 -> start就绪/准备 -> 运行 -> wait…阻塞 -> 死亡
8. 什么是公平锁和非公平锁?
- 公平锁:等待锁时间长的线程,更容易获得锁;
- 非公平锁:等待锁时间长和短的线程,获得锁的几率相同。
9. synchronized和同步锁的区别?
- 上锁:synchronized是自动上锁和解锁的,同步锁是手动完成的。
- 性能:同步锁性能高于synchronized
- 使用:synchronized使用简单,功能单一,同步锁提供更多方法,使用灵活。
10. 线程同步的三大特征:
- 原子性:(一系列指令要么全部执行,要么全部不执行)
- 可见性:(线程中的数据修改后,其他线程也可以读取最新的数据)
- 有序性:(线程中的指令按顺序执行,不会出现乱序)
11. synchronized与volatile的区别?
- 修饰的对象:synchronized修饰代码块或方法,volatile只能修饰变量
- 保证的特性:synchronized保证原子性、可见性;volatile不能保证原子性,保证可见性和有序性
- 性能:volatile是轻量级的线程同步方式,性能更高
12. 悲观锁和乐观锁?
- 悲观锁:对于并发间操作产生的线程安全s问题持悲观状态,悲观锁认为竞争总会发生,因此每次对资源进行操作时,都会给资源加锁。
- 乐观锁:对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不会发生,因此不持有锁,使用版本号和CAS机制解决:
- 版本号机制:对资源设置版本,每次修改后更新版本,修改后与之前版本比较,相同提交,不同不提交。
- CAS机制:CompareAndSwap:通过内存偏移量获取数据原始值,通过原始值计算预期值和实际值比较,相同更新,不同不更新。进入循环等待状态,直到比较相等。
13. 线程的等待和通知机制如何实现
可以通过锁对象调用Object类的方法(如果不是锁对象会出现illegalMonitorStateException):
- wait() 让当前线程等待,直到被通知
- wait(long) 让线程等待,知道被通知或超时
- notify() 随机唤醒一个线程
- notify() 唤醒所有线程
14. 什么是生产者消费者模式,解决了什么问题,实现的思路是什么?
是一种设计模式,解决了两点(线程、进程、服务器)之间的数据通信问题
- 生产数据的点叫生产者,使用数据的点叫消费者,生产者和消费者之间存在速度不一致的问题,生产者速度过快,消费者速度慢,会浪费大量资源。反之,生产者慢,消费者速度快,消费者浪费时间请等待。
- 解决的问题:
- 解耦,生产者和消费者之间加入缓冲区,不需要直接调用
- 忙闲不均,协调生产者和消费者之间的速度。
- 节约资源,提高性能,减少生产者的资源浪费,减少消费者的等待时间。
- 实现思路:
- 设计缓冲区,设计临界值;
- 生产者生产的数据存入缓冲区,如果缓冲区达到临界值,生产者就等待;否则就通知消费者来消费。
- 消费者从缓存区取出数据,如果缓冲区空了,消费者等待;否则就通知生产者继续生产。
15. 阻塞队列是什么,有什么作用?
是一种特殊的集合,会自动阻塞队列添加和删除数据的线程,也可以自动通知。
16. ThreadLocal是什么,有什么作用?
线程局部变量,也是解决线程同步问题的一种方式。
- 锁机制,多个线程排队访问一个资源,以时间换空间。
- ThreadLocal多个线程并发访问自己的独立资源,以空间换时间。
- 每个线程内部有Map集合ThreadLocalMap,获得数据时候返回当前线程的Map集合,局部变量是保存到当前线程的Map中的
17. 讲讲强软弱虚引用
- 强引用(strongReference):一般情况下创建的对象都具有强应用,强引用不会被垃圾回收器回收,即使空间不足,jvm也不会回收它它,只会抛出OutOfMemoryError,使线程异常终止。如果想要终止强引用和某个对象之间的引用,可以显示的将引用赋值为null,这样jvm就会在合适的时间回收该对象。
- 软引用(softReference):软引用只有在内存不够的情况下才会被回收,一般用于缓存。
- 弱引用(WeakReference):弱引用在JVM进行垃圾回收时,不论空间是否充足都会回收。可以用来缓存临时数据。
- 虚引用(PhantomReference):形同虚设,任何情况都会被垃圾回收机制回收,主要用于跟踪垃圾回收器的活动。
线程池
1. 线程池的作用,常见的线程池有哪些?
线程是一种有限的宝贵的系统资源,创建线程需要很多时间和系统资源,需要对线程进行回收利用,降低对系统资源的消耗,提升服务器的执行效率。
顶层接口:Executor,有一个execute(Runnable)将任务交给线程池执行。
子接口ExecutorService,有:shutdown() 和 shutdwnNow()方法停止线程池。
ExecutorService的子接口:scheduleExecutorService:
在固定的延迟时间后执行
scheduleWithFixedDelay(执行的Runnable任务,初始的延迟,延迟,时间单位)
在固定的周期执行
scheduleAtFixedRate(执行的Runnable任务,初始的延迟,周期,时间单位)
工具类:
Executors 提供了几个静态方法,帮助方便的创建线程池
线程池类型 说明 ExecutorService newCachedThreadPool() 创建线程个数不限的线程池 ExecutorService newFixedThreadPool(int) 创建个数固定的线程池 ExecutorService newSingleThreadExecutor() 创建单个线程的线程池 ScheduledExecutorService newScheduledThreadPool(int) 创建可以调度的线程池 ExecutorService newWorkStealingPool() 创建工作窃取机制的线程池
2. 解释线程池的几个参数
参数 说明 corePoolSize 核心线程数(线程池初始的线程数,核心线程会一直保留) maximumPoolSize 最大线程数(线程池最大线程数,非核心线程可能被干掉) keepAliveTime 保持存活的时间(非核心线程如果在此时间内没有接收任务,就可能被干掉) unit 时间单位 BlockingQueue 用于保存任务的阻塞队列 ThreadFactory 线程工厂,用于创新线程 RejectedExecutionHandler 拒绝策略(任务数超过最大线程数,拒绝执行任务的方式)
3. 线程池如何进行配置
优化配置:
核心线程数 和cpu核心数以及任务数量和执行时间相关
核心线程数 = cpu核心数 * n
n >= 1 上限和任务数以及执行时间相关
本机的cpu核心数 Runtime.getRuntime().availableProcessors();
最大线程数
最大线程数和核心线程数相同,减少系统创建线程和销毁线程的时间和资源,提高性能
keepAliveTime
如果最大线程数和核心线程数相同,可以设置为0;
否则可以尽量大一点,减少系统创建线程和销毁线程的时间和资源
BlockingQueue
设置为LinkedBlockQueue,任务经常添加和删除时,性能更高
设置为SynchorousQueue,能处理的任务数更多,吞吐量更高(每秒处理请求数)
4. 讲讲对线程池原理的理解
步骤:
1) 通过线程池的execute执行任务
2) 通过调用线程池的addWorker方法添加工作线程
3) 同时把Runnable执行任务添加到workQueue中
4) 创建Worker添加到HashSet中,Worker中包含线程
5) 启动Worker中的线程
6) 线程调用runWorker方法
7) runWorker中判断任务不为空就执行任务
8) 任务如果为空,就调用getTask()方法取任务
9) 循环不断调用workerQueue阻塞队列的take()方法
10) 任务队列为空就自动阻塞当前线程,直到任务队列不为空唤醒线程去执行任务
网络通信
1. 网络七层模型
1) 物理层,底层硬件
2) 数据链路层,通信的介质
3) 网络层,寻址和路由, IP协议
4) 传输层,连接和数据通信,TCP协议\UDP协议
5) 会话层,管理会话
6) 表示层,处理数据格式、加密
7) 应用层,程序之间的通信,http协议\ftp协议\smtp协议\pop3协议
2. 三次握手和四次挥手
3. 为什么要三次握手?(扩展)
1.一次不行,服务端没有应答
2.二次不行,避免重复连接,客户端发送同步请求后,因为网络问题,没有及时达到服务端,重复发送请求,服务端应答后可能出现重复连接的情况。必须有三次,可以确定前面的历史报文,确认是之前的请求,再继续连接。
4. Socket编程的流程
1.1服务器端
(1) 创建ServerSocket对象,绑定监听端口;
(2) 通过accept()方法监听客户端请求;
(3) 连接建立后,通过输入流读取客户端发送的请求信息;
(4) 通过输出流向客户端发送相应信息;
(5) 关闭响应资源。
1.2 客户端
(1) 创建Socket对象,指明需要连接的服务器地址和端口;
(2) 连接建立后,通过输出流向服务器端发送请求信息;
(3) 通过输入流获取服务器端返回的响应信息;
(4) 关闭响应资源。