多线程(面试)
- 1. 进程与线程的区别?
- 2. 为什么要用多线程?
- 3. 多线程创建方式?
- 4. 是继承Thread类好还是实现Runnable接口好?
- 5. 你在哪里用到了多线程?
- 6. 什么是多线程安全?
- 7. 如何解决多线程之间线程安全问题?
- 8. 为什么使用线程同步或使用锁能解决线程安全问题呢?
- 9. 什么是多线程之间同步?
- 10. 什么是同步代码块?
- 11. 多线程同步的分类?
- 12. 同步代码块与同步函数区别?
- 13. 同步函数与静态同步函数区别?
- 14. 什么是多线程死锁?
- 15. Wait()与Notify ()区别?
- 16. Wait()与sleep()区别?
- 17. Lock与Synchronized区别?
- 18. Condition用法
- 19. 如何停止线程?
- 20. 什么是守护线程?
- 21. join()方法作用
- 22. 线程三大特性
- 23. 什么是Volatile作用
- 24. 什么是AtomicInteger?
- 25. 什么是ThreadLocal?
- 26. 什么是线程池?
- 27. 线程池作用
- 28. 线程池四种创建方式
- 29. 说说JDK1.5并发包
- 30. 锁的种类
1. 进程与线程的区别?
进程是所有线程的集合,每一个线程是进程中的一条执行路径,线程只是一条执行路径。
2. 为什么要用多线程?
提高程序效率。
3. 多线程创建方式?
继承Thread
或Runnable
接口。
4. 是继承Thread类好还是实现Runnable接口好?
Runnable
接口好,因为实现了接口还可以继续继承。继承Thread
类不能再继承。
5. 你在哪里用到了多线程?
主要能体现到多线程提高程序效率。
举例:分批发送短信、迅雷多线程下载等。
6. 什么是多线程安全?
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。做读操作是不会发生数据冲突问题。
7. 如何解决多线程之间线程安全问题?
使用多线程之间同步或使用锁(lock
)。
8. 为什么使用线程同步或使用锁能解决线程安全问题呢?
将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。被包裹的代码执行完成后释放锁,然后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
9. 什么是多线程之间同步?
当多个线程共享同一个资源,不会受到其他线程的干扰。
10. 什么是同步代码块?
就是将可能会发生线程安全问题的代码,给包括起来。只能让当前一个线程进行执行,被包裹的代码执行完成之后才能释放锁,然后才能让其他线程进行执行。
11. 多线程同步的分类?
- 使用同步代码块
private static int trainCount = 100;
// 自定义多线程同步锁
private static Object mutex = new Object();
public static void main(String[] args) {
sale();
}
public static void sale() {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
}
}
}
- 使用同步函数
在方法上修饰synchronized
称为同步函数。
private static int trainCount = 100;
public static void main(String[] args) {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
}
}
- 静态同步函数
方法上加上static
关键字,使用synchronized
关键字修饰 为静态同步函数
静态的同步函数使用的锁是 该函数所属字节码文件对象 。
12. 同步代码块与同步函数区别?
- 同步代码使用自定锁(明锁)。
- 同步函数使用this锁。
13. 同步函数与静态同步函数区别?
- 同步函数使用
this
锁。 - 静态同步函数使用字节码文件,也就是类
.class
。
14. 什么是多线程死锁?
- 同步中嵌套同步,无法释放锁的资源。
- 解决办法:同步中尽量不要嵌套同步。
15. Wait()与Notify ()区别?
Wait
让当前线程由运行状态变为等待状态,和同步一起使用。Notify
唤醒现在正在等待的状态,和同步一起使用。
16. Wait()与sleep()区别?
- 对于
sleep()
方法,我们首先要知道该方法是属于Thread
类中的。而wait()
方法,则是属于Object
类中的。 sleep()
方法导致了程序暂停执行指定的时间,让出cpu
给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。- 在调用
sleep()
方法的过程中,线程不会释放对象锁。 - 当调用
wait()
方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()
方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
17. Lock与Synchronized区别?
Lock
接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。Lock
接口能被中断地获取锁 与synchronized
不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。Lock
接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。
18. Condition用法
Condition
的功能类似于在传统的线程技术中的,Object.wait()
和Object.notify()
的功能
Condition condition = lock.newCondition();
res.condition.await(); 类似wait
res.condition.Signal() 类似notify
Signalall notifyALL
19. 如何停止线程?
- 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
- 使用
stop
方法强行终止线程(这个方法不推荐使用,因为stop
和suspend
、resume
一样,也可能发生不可预料的结果)。 - 使用
interrupt
方法中断线程。 线程在阻塞状态。
20. 什么是守护线程?
Java
中有两种线程,一种是用户线程,另一种是守护线程。
当进程不存在或主线程停止,守护线程也会被停止。
使用setDaemon(true)
方法设置为守护线程。
21. join()方法作用
join
作用是让其他线程变为等待,只有当前线程执行完毕后,等待的线程才会被释放。
22. 线程三大特性
多线程有三大特性,原子性、可见性、有序性。
- 原子性:保证数据一致性,线程安全。
- 可见性:对另一个线程是否可见。
- 有序性:线程之间执行有顺序。
23. 什么是Volatile作用
Volatile
关键字的作用是变量在多个线程之间可见。
24. 什么是AtomicInteger?
AtomicInteger
原子类。
25. 什么是ThreadLocal?
ThreadLocal
提高一个线程的局部变量,访问某个线程拥有自己局部变量。
当使用ThreadLocal
维护变量时,ThreadLocal
为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal
的接口方法: ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
void set(Object value)
设置当前线程的线程局部变量的值。public Object get()
该方法返回当前线程所对应的线程局部变量。public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0
新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected
的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()
或set(Object)
时才执行,并且仅执行1次。ThreadLocal
中的缺省实现直接返回一个null
。
26. 什么是线程池?
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。
27. 线程池作用
基于以下几个原因在多线程应用程序中使用线程是必须的:
- 线程池改进了一个应用程序的响应时间。由于线程池中的线程已经准备好且等待被分配任务,应用程序可以直接拿来使用而不用新建一个线程。
- 线程池节省了
CLR
为每个短生存周期任务创建一个完整的线程的开销并可以在任务完成后回收资源。 - 线程池根据当前在系统中运行的进程来优化线程时间片。
- 线程池允许我们开启多个任务而不用为每个线程设置属性。
- 线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用。
- 线程池可以用来解决处理一个特定请求最大线程数量限制问题。
28. 线程池四种创建方式
Java
通过Executors
(jdk1.5并发包)提供四种线程池,分别为:
线程池种类 | 描述 |
---|---|
newCachedThreadPool | 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 |
newFixedThreadPool | 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 |
newScheduledThreadPool | 创建一个定长线程池,支持定时及周期性任务执行。 |
newSingleThreadExecutor | 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO , LIFO , 优先级)执行。 |
29. 说说JDK1.5并发包
线程池种类 | 描述 |
---|---|
Lock | 锁 |
Executors | 线程池 |
ReentrantLock | 一个可重入的互斥锁定 Lock ,功能类似synchronized ,但要强大的多。 |
Condition | Condition 的功能类似于在传统的线程技术中的,Object.wait() 和Object.notify() 的功能。 |
ConcurrentHashMap | 分段HasMap |
AtomicInteger | 原子类 |
BlockingQueue | BlockingQueue 通常用于一个线程生产对象,而另外一个线程消费这些对象的场景 |
ExecutorService | 执行器服务 |
30. 锁的种类
自旋锁:
自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。
互斥锁:
所谓互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5
之前, 我们通常使用synchronized
机制控制多个线程对共享资源Lock
接口及其实现类ReentrantLock
。
可重入锁:
可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。在JAVA
环境下 ReentrantLock
和synchronized
都是 可重入锁。
悲观锁:
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。
乐观锁:
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库 性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。 而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version
)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version
” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如 果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。