1 synchronized 关键字
- 使用 synchronized 关键字修饰的方法为同步方法,由于 java 的每个对象都有一个内置锁,当此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。( synchronized 关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类)
- 有 synchronized 关键字修饰的语句块被称为同步代码块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
2 什么是可重入锁?
可重入锁表示锁具有可重入性,例如 synchronized,ReentrantLock 都是可重入锁。可重入性表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个 synchronized 方法时,比如说 method1,而在 method1 中会调用另外一个 synchronized 方法 method2,此时线程不必重新去申请锁,而是可以直接执行方法 method2。
class MyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}
method1 和 method2 都用 synchronized 修饰了,假如某一时刻,线程A执行到了 method1,此时线程A获取了这个对象的锁,而由于 method2 也是 synchronized 方法,假如 synchronized 不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
而由于 synchronized 和 Lock 都具备可重入性,所以不会发生上述现象。
3 什么是可中断锁?
可中断锁即可以相应中断的锁。Lock 是可中断锁,而 synchronized 不是可中断锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
4 什么是公平锁?
公平锁即尽量以请求锁的顺序来获取锁。比如同时有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这种就是公平锁。非公平锁无法保证锁的获取是否按照请求锁的顺序进行的,这样就可能导致某个或者一些线程永远获取不到锁。
synchronized 是非公平锁,它无法保证等待的线程获取锁的顺序。至于 ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
5 synchronized 什么情况下会释放锁?
- 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
- 线程执行发生异常,此时 JVM 会让线程自动释放锁。
- 调用 wait 方法,在等待的时候立即释放锁,方便其他的线程使用锁.
6 Lock 和 synchronized 的区别?
- Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现
- synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock() 去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁
- Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到
- Lock 可以提高多个线程进行读操作的效率。在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized。所以说,在具体使用时要根据适当情况选择
- Lock 可以设置等待锁的时间,而 synchronized 不行
7 什么是 CAS?
CAS(Compare And Swap) 无锁算法: CAS 是乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS 有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
8 线程池的作用有哪些?
线程池的作用: 在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。线程池的作用如下:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。
9 什么是线程安全以及解决机制?
线程安全是指在多线程环境中能永远保证程序的正确性,其实,只有存在共享数据时才需要考虑线程安全问题。当一个类被多个线程进行访问并且正确运行,它就是线程安全的。
如何解决?
- 加锁:
a 锁能使其保护的代码以串行的形式来访问,当给一个复合操作加锁后,能使其成为原子操作。一种错误的思想是只要对写数据的方法加锁,其实这是错的,对数据进行操作的所有方法都需加锁,不管是读还是写
b 加锁时需要考虑性能问题,不能总是一味地给整个方法加锁 synchronized 就了事了,应该将方法中不影响共享状态且执行时间比较长的代码分离出去
c 加锁的含义不仅仅局限于互斥,还包括可见性。为了确保所有线程都能看见最新值,读操作和写操作必须使用同样的锁对象
- 不共享状态:
无状态对象: 无状态对象(没有字段和引用)一定是线程安全的,因为不会影响到其他线程
线程关闭: 仅在单线程环境下使用
- 不可变对象:
可以使用 final 修饰的对象保证线程安全,由于 final 修饰的引用型变量(除 String 外)不可变是指引用不可变,但其指向的对象是可变的,所以此类必须安全发布,也即不能对外提供可以修改 final 对象的接口
10 死锁
参考:什么是死锁?死锁的预防?
11 关键字 volatile
12 synchronized 的实现原理
13 JVM 对 synchronized 的优化
14 Synchronized 与 Volatile 之间的区别
- Volatile 仅能使用在变量级别; Synchronized 则可以使用在变量、方法和同步代码块级别上。
- Volatile 仅能实现变量的修改可见性和有序性,并不能保证原子性;Synchronized 则可以保证变量的可见性、有序性、原子性。
- Volatile 不会造成线程的阻塞;Synchronized 可能会造成线程的阻塞。
- Volatile 标记的变量不会被编译器优化; Synchronized 标记的变量可以被编译器优化。
15 什么是 Java 内存模型?
16 Java 并发包都有哪些?
17 线程的5种状态
- 新建(NEW):新创建了一个线程对象
- 可运行(RUNNABLE):线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start() 方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权
- 运行(RUNNING):可运行状态的线程获得了cpu 时间片,执行程序代码
- 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行状态,才有机会再次获得 cpu timeslice 转到运行状态
- 死亡(DEAD):线程 run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生
三种阻塞情况如下:
- 等待阻塞:运行的线程执行 o.wait() 方法,JVM 会把该线程放入等待队列中
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池中
- 其他阻塞:运行的线程执行 Thread.sleep(long ms) 或 t.join() 方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行状态
18 ABA 问题的解决方案
ABA 问题来源于 CAS 操作,CAS 操作需要在操作值的时候,检查值有没有发生变化,如果没有发发生变化则更新。如果一个值原来是 A,变成了 B,又变成了 A,那么使用 CAS 进行检查时会认为它的值没有变化,但是实际上却变了。
如何解决呢?使用版本号,在每次变量更新时把版本号加1,那么 A-B-A 就会变成 1A-2B-3A。
19 多线程的实现方式
20 线程间的通信方式
参考:解释一下锁的一些基本概念:可重入锁、可中断锁、公平锁、读写锁
synchronized和ReentrantLock有什么区别
Java并发 - 什么是线程安全(一).
线程的5种状态详解