文章参考的是javaGuide,
https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/multi-thread/2020%E6%9C%80%E6%96%B0Java%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93.md
这里记录属于个人的查漏补缺,如果小伙伴需要更加全面和详细的资料,请看上面的链接呦!
1、为什么程序计数器是私有的?
作用:
- 通过它来读取下一条指令。
- 多线程情况下,记录当前线程的执行位置。
所以他的作用就是线程切换之后能恢复到正确的执行位置。
2、为什么虚拟机栈和本地方法栈是线程私有的?
- 虚拟机栈:每个方法在执行的时候都会创建一个栈帧来存储局部变量表、操作数、引用等。方法的调用到执行过程就对应着一个栈帧在虚拟机栈中的入栈和出栈过程。
- 本地方法栈:运行的是native方法,虚拟机栈运行的是java方法。
所以他们的作用就是为了保证线程中的局部变量不被其他线程访问。
3、并发和并行的区别?
- 并发:同一时间段,多个任务都在执行
- 并行:单位时间内,多个任务都在同时执行
4、为什么要使用多线程呢?
-计算机底层:因为线程是轻量级进程,上下文切换代价小。而且多核cpu就意味着多个线程可以同时执行。
- 发展趋势:多线程并发编程是高并发系统的基础。
使用多线程带来的问题:
使用并发编程的目的是为了提高运行速度,但是并发编程也不是总能提高运行速度的,还有可能出现内存泄漏、死锁、线程不安全的问题。
什么是上下文切换?
多线程编程中一般来说线程的个数大于cpu核心数,一个cpu核心在任意时刻只能被一个线程使用,所以cpu采用了时间片轮转策略。当线程的时间片用完之后就会重新处于就绪状态,让给其他线程使用,这就是上下文切换。
5、什么是线程死锁?怎么解决?
死锁就是多个线程被阻塞,互相等待对方释放资源。
死锁的四个条件:
- 互斥条件:一个资源任意时刻只能被一个线程占用;
- 请求和保持条件:线程被阻塞时,它持有的资源是不释放的;
- 不剥夺条件:不能强行剥夺线程持有的资源,必须是他自己用完释放;
- 循环等待条件:形成闭环;
怎么解决?
打破这四个条件。 - 互相条件:这个不能破坏
- 请求和保持条件:一次性申请所有的资源
- 不剥夺条件:占用资源的进程申请其他资源时,如果申请不到就释放自己持有的资源
- 循环等待条件:按序申请
6、为什么不直接调用run()方法?
对于线程来说,new的时候创建一个线程,调用start()方法线程从创建状态变为就绪态,然后获得cpu执行权之后调用run()方法变为运行态。这是多线程工作。
如果直接调用run()方法,相当于在一个main线程下执行普通的方法,就不是多线程工作了。
7、对synchronized关键字的理解?
用来解决多个线程之间访问资源的同步性,被它修饰的方法或者代码块在任意时刻只能由一个线程执行。
在java的早期版本它是属于重量级锁,因为synchronized是由监视器monitor实现的,依赖于底层的操作系统。如果要挂起或者唤醒一个线程,需要操作系统进行线程之间的切换,从用户态转为内核态,代价高。目前对它进行了优化。
怎么使用的?
1、修饰实例方法:对当前对象实例进行加锁。
synchronized void method() {
//业务代码
}
2、修饰静态方法:给当前类进行加锁,会作用于类的所有实例对象。
synchronized void staic method() {
//业务代码
}
3、修饰代码块:synchronized(this)表示进入代码块之前要获得给定对象的锁;synchronized(类.class)表示进入代码块之前要获得当前class的锁。
synchronized(this) {
//业务代码
}
总结:synchronized关键字加到static静态方法和*synchronized(class)*代码块上是给class类加锁。加到实例方法是给对象实例加锁。
双重校验锁实现对象单例:
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
8、构造方法可以使用synchronized关键字修饰吗?
**不可以。**因为构造方法本身就是安全的,不存在这一说。
synchronized底层原理:
同步语句块使用monitor监视器的monitorEnter和monitorExit指令。执行monitorEnter时,会尝试获取对象的锁,如果锁的计时器为0则表示锁可以被获取,获取后将锁的计时器加1.释放锁时会将锁的计时器设为0.
被修饰的方法是通过acc_synchronzied标识符标识的。
9、jdk1.6之后对synchronized关键字底层做了那些优化?
锁的状态变为四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这种划分是为了提高获取锁和释放锁的效率。这些信息都是存在对象头的markword中的。
10、谈谈synchronized和reentrantLock的区别?
- 他们都是可重入锁:就是可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个锁还没被释放,当这个线程想再次获取的时候,可以获取,不是可重入锁的话,就会造成死锁。同一个线程每次获取锁,锁的计时器就会加1,等到计时器变为0才能释放锁。
- synchronized是依赖于jvm的,对它的优化也是在虚拟机层面体现的,没有直接暴露出来。reentrantlock是依赖于api的,可以查看源代码,看如何实现的。
- reentrantlock相比synchronized多加了一些高级功能:
(1)等待可中断:等待的线程可以放弃等待
(2)实现公平锁:synchronized只能是非公平锁,reentrantlock可以指定公平锁。通过**ReentrantLock(Boolean fair)**构造方法来指定。
(3)可以绑定多个条件:synchronized可以于wait(),notify()等实现等待通知机制,reentrantlock可以借助于condition接口和newCondition()方法实现。
11、ThreadLocal(这一块不太理解)
ThreadLocal类是实现每一个线程都有自己的专属本地变量,让每个线程绑定自己的值。从而避免了线程安全问题。threadlocal可以比喻成盒子,盒子中存放的是每个线程的私有数据。
两个人去收集宝物,公用一个袋子装肯定会有争持,每人分配一个袋子就不会出现这个问题了。
threadLocal原理:
thread类中有一个threadlocals和inheritableThreadLocals这两个变量,他们都是ThreadLocalMap类型的变量。默认这两个变量都为null,只有调用get()和set()方法时才会创建他们,实际上调用这两个方法时,调用的是threadlocalmap类对应的get()、set()方法。
每个threadLocal中都具备一个threadLocalMap,而threadlocalmap可以存储threadlocal为key,object对象为value的键值对。
threadlocal中使用key为threadlocal的弱引用,value为强引用。所以在垃圾回收的时候,key会被清理掉,而value不会被清理掉。这样就发生了内存泄漏。
解决方法:threadloacalmap在调用get()和set()remove()方法时,会清理掉key为null的记录。
12、AtomicInteger类的原理
此类只要利用CAS+volatile和native方法来保证原子性。从而避免了synchronized的高开销,大大提高了效率。
13、AQS介绍
AQS是用来构建锁和同步器的框架。
其核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态。其他申请资源的线程就会被加入队列等待,线程的阻塞等待以及被唤醒时锁的分配机制,是由AQS中的CLH队列锁实现的。
CLH时一个虚拟的双向队列,双向队列的意思就是不存在队列实例,只存在节点之间的关联关系。AQS将每条请求共享资源的线程封装成一个CLH锁队列的一个节点来实现锁的分配。