【面试题总结二】并发控制

前言

今天看了上一篇的阅读量很快就100多了,我的内心是崩溃的。。。。在这里插入图片描述就像这张图一样,我是个颜值主播啊,写的OJ上的题解没人看,写面试题总结比写题受欢迎多了。
好吧,开个玩笑,其实有人看了我还是比较紧张的,因为我写这个的初衷是给我自己看的,大多都是我自己的理解,很多概念都是不准确的,所以请大家看了之后有偏差的地方指正出来,不甚感激!

线程,协程

线程其实就是进程的一个执行流,进程是程序的一次执行。JAVA实现线程的两种种常见方式:继承Thread类/实现Runnable接口。
协程:这个东西,其实现在用的很多了,尤其是go语言里面,协程会被大量的使用。协程可以理解为一种更加轻量级的线程。线程的调度是由CPU完成的,在调度的时候会涉及到线程的切换,用户态到内核态的转换等。所以这是比较消耗资源的,协程的调度由某个线程来完成,完全发生在用户态。go语言中的协程叫做goroutine,通过go关键字启动,java里面也有对应的,不过我还没有用过。协程在阻塞的时候,会把数据保存在当前的栈上,然后恢复的时候继续执行,所以消耗很少。

虚拟机内存中,哪些是线程私有的?

这个其实搞清楚每一块的作用就很easy了。首先虚拟机的内存可以划分为:堆,方法区,虚拟机栈,本地方法栈,程序计数器。其中前两个是线程共有,后三个是线程私有。
首先每个线程执行,都需要有个栈,这个栈就是虚拟机栈,这个肯定是私有的。
本地方法栈是用来执行一些本地的方法(这个简单的解释一下,因为JAVA的执行是依托虚拟机的,所以在操作系统和程序之间就会有一层虚拟机隔离,这是java可以实现高度跨平台的原因,但是也因为这个使得java对系统的操作变得很差,不能深入底层,所以java提供了一种机制:native方法,这个方法可以是C/C++等编写的本地方法,java可以调用)。显然,执行这些方法也是需要栈的,所以线程私有。
程序计数器:这个就是标识线程执行到了那一条指令,当然在java里面应该叫做字节码。这个当线程阻塞的时候,肯定要存一下,因为等到CPU下次调用需要知道从哪里开始,所以这个也是线程私有的。
其他的都是线程公共访问区域了,堆就是放各种数据的地方,方法区也能算对的一部分。

sleep和wait

sleep和wait都是用来阻塞线程的,最大的区别在于两点
(1)sleep不会释放线程持有的锁,wait会释放
(2)sleep阻塞的线程会自然苏醒,当sleep执行完线程就解除阻塞,wait需要其他线程主动唤醒(通过notify/notifyAll)

什么叫做守护线程

顾名思义就是守护其他现成的线程,比如垃圾回收。也叫做后台线程,可以调用setDaemon(true),设置一个线程为守护线程。当所有的用户线程退出之后,守护线程也会自动退出。

乐观锁和悲观锁

乐观锁:其实都是一种概念,乐观锁认为并发的时候,不会有很多次修改数据操作,所以读取数据不用加锁,真正修改了就回滚读操作。乐观锁在Java里面有一种cas实现
悲观锁:和乐观锁相反,认为修改会随时发生,任何时候都需要锁定数据。这个java采用Synchronized实现。

自旋锁

自选听起来很高大上,其实就是等待的意思。这个是基于一种假设,正在持有锁的对象不会长时间占用,会在很短的时间释放资源,那么其他需要获取该锁的线程只需要原地等待一会就行,这个原地等待就是自选操作。
优点:不用阻塞线程,这样就不会有很多其他的操作,比如保存那些线程私有的数据啊等等。
缺点:首先一个很明显的缺点就是CPU会做无用功,因为自旋。并且如果一个线程长时间占有锁,那么这个性能就会大大下降。
JDK关于自旋锁的设计:早期的jdk版本都是给自旋的时间设置了一个阈值,超时就阻塞线程,jdk1.6之后添加了自适应的自旋时间,会动态调整。

什么是CAS

cas的全称Compare And Swap。比较交换操作,是乐观锁的一种实现。核心的思想就是给需要读取的值加上一个状态,比如版本号,然后写入的时候通过比较版本号来发现值有没有被修改过,如果被修改过,那么说明之前读入的值没用了,不做写入。
举个例子:比如A=1,确定A在内存中的数据V,接着操作A得到了一个变量B,比较V和A的是不是相等,如果相等可以认为A没有被修改过,把B写入,也叫交换,否则不行。
缺点:ABA问题,就是内存中的那个V其实被其他线程修改成了B,然后有修改了回来变成A,这样t1回来的时候,发现还是A,认为没有被修改,其实是被修改过的
注意,CAS是一个原子操作。

Synchronized

(1)修饰同步代码块,其实这可以说是一个临界区域,每个时间只能由一个线程进入临界区域。
(2)修饰方法,则该方法是一个同步方法,多线程调用时需要同步。

Volatile

被它修饰的变量线程本地的缓存时失效的,变量不会被暂存在线程本地,会被直接刷进主内存。也可以所被它修饰的关键字的修改时所有线程可见的。是一种轻量级数据同步方式。他还有一个作用就是禁止指令重排,这个不多说了,有兴趣的可以看看。

ThreadLocal

看名字就知道了作用,线程本地私有的区域,因为线程中的数据都是先从主内存中拷贝到本地,然后操作完之后再刷新回主内存(当然,如果没有就不会被刷新回去,具体可以看看java虚拟机的实现),Threadlocal是给线程一个私有的空间,提供get和set方法。

偏向锁

这个主要是为了消除一个线程内部有那种递归的调用,如果一个递归函数需要获得锁,那么它进入下一层递归 依然需要获得锁,这叫锁的重入。这种锁是可重入锁,偏向锁的目的就是为了消除该线程重入时的开销。看起来像是偏袒了这个线程,所以叫做偏向锁。可以发现,偏向锁的优化是在代码层面的。

锁的优化

(1)减少持有锁的时间
(2)细化锁的粒度,这个可以简单的说一下,一个线程访问资源需要对资源加锁,但是它不一定就是访问这个资源的所有信息,如果另外一个线程也来访问这个资源,但是和访问的不冲突,由于第一个线程堆资源进行了全局锁定,所以第二个线程只能等待。为了解决这种问题,可以对资源分段加锁,java中有一个典型的实现ConcurrentHashMap。
(3)读写分离。

Join

这个我认为只要是控制线程的执行顺序的,在一个线程中调用另外一个线程,那么这两个线程谁先退出呢?答案是不一定,因为你得看谁的任务完成了,谁就提前退出了。但是如果被调线程调用了自己join方法,那么该线程就只能等到被调线程结束之后再退出。

线程池

这个我没怎么用过,只知道有四种,阻塞队列,拒绝策略等等。
大概就是这么多了,其他的想起来再补。

发布了370 篇原创文章 · 获赞 48 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/weixin_41863129/article/details/104347057
今日推荐