Java面试题系列——JavaSE面试题(线程四)

1、并发与并行的区别

        概念:(1)并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于状。.这种方式我们称之为并发(Concurrent)。(2)并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

        区别:(1)并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。(2)在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。(3)若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。

2、关于多线程锁升级的理解

        概念:

        (1)JVM优化synchronized的运行机制:当JVM检测到不同的竞争状态时,就会根据需要自动切换到合适的锁,这种切换就是锁的升级。升级是不可逆的,也就是说只能从低到高,也就是偏向-->轻量级-->重量级,不能够降级。

       (2) 锁级别:无锁->偏向锁->轻量级锁->重量级锁。

        (3)CAS(compare and swap):当我们需要对内存中的数据进行修改操作时,为了避免多线程并发修改的情况,我们在对它进行修改操作前,先读取它原来的值E,然后进行计算得出新的的值V,在修改前去比较当前内存中的值N是否和我之前读到的E相同,如果相同,认为其他线程没有修改过内存中的值,如果不同,说明被其他线程修改了,这时,要继续循环去获取最新的值E,再进行计算和比较,直到我们预期的值和当前内存中的值相等时,再对数据执行修改操作。

        (4)对象头:synchronized用的锁是存在java对象头里的。32位java对象头结构如下表所示:

        对于64位的java对象头其余信息基本不变,只是中间有关于对象hashcode值和之后加锁信息的位数加大以外,其他基本不变。64位虚拟机系统下java对象头在不同锁状态下的状态变化如下表所示:

        (5)偏向锁:获取偏向锁流程:当一个线程访问同步块时,会先判断锁标志位是否为01,如果是01,则判断是否为偏向锁,如果是,会先判断当前锁对象头中是否存储了当前的线程id,如果存储了,则直接获得锁。如果对象头中指向不是当前线程id,则通过CAS尝试将自己的线程id存储进当前锁对象的对象头中来获取偏向锁。当cas尝试获取偏向锁成功后则继续执行同步代码块,否则等待安全点的到来撤销原来线程的偏向锁,撤销时需要暂停原持有偏向锁的线程,判断线程是否活动状态,如果已经退出同步代码块则唤醒新的线程开始获取偏向锁,否则开始锁竞争进行锁升级过程,升级为轻量级锁。

        (6)轻量级锁:当出现锁竞争时,会升级为轻量级锁。在升级轻量级锁之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间即将对象头中用来标记锁信息相关的内容封装成一个java对象放入当前线程的栈帧中,这个对象称为LockRcord,然后线程尝试通过CAS将对象头中mark word替换为指向锁记录(lockrecord)的指针。如果成功则当前线程获取锁,如果失败则使用自旋来获取锁。自旋其实就是不断的循环进行CAS操作直到能成功替换。所以轻量级锁又叫自旋锁。栈上分配LockRecord: lockrecord中包含了对象的引用地址。对象头中markword替换锁记录指针成功之后如下图:

替换成功之后将锁标志位改为00 表示获取轻量级锁成功。

lockrecord的作用:在这里实现了锁重入,每当同一个线程多次获取同一个锁时,会在当前栈帧中放入一个lockrecord,但是重入是放入的lockrecord关于锁信息的内容为null,代表锁重入。当轻量级解锁时,每解锁一次则从栈帧中弹出一个lockrecord,直到为0.

轻量级锁重入之后如下图:

当通过CAS自旋获取轻量级锁达到一定次数时,JVM会发生锁膨胀升级为重量级锁。

原因:不断的自旋在高并发的下会消耗大量的cpu资源,所以jvm为了节省cpu资源,进行了锁升级。将等待获取锁的线程都放入一个等待队列中来节省cpu资源。

(7)重量级锁:在重量级锁中将LockRecord对象替换为了monitor对象的实现。主要通过monitorenter和monitorexit两个指令来实现。需要经过系统调用,在并发低的情况下效率会低。通过JDK可以查看ObjectMonitor对象的结构:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL; //拥有当前对象的线程
    _WaitSet      = NULL; //阻塞队列
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //有资格成为候选资源的线程队列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

使用monitor加锁如下图:

重量级锁在进行锁重入的时候每获取到锁一次会对monitor对象中的计数器+1,等锁退出时则会相应的-1,直到减到0为止,锁完全退出。

几种锁状态优缺点对比

总结

综上,我们发现偏向锁,轻量级锁(又称自旋锁或无锁),重量级锁都是synchronized锁锁实现中锁经历的几种不同的状态。

三种锁状态的场景总结:

  • 只有一个线程进入临界区 -------偏向锁
  • 多个线程交替进入临界区--------轻量级锁
  • 多个线程同时进入临界区-------重量级锁 

3、synchronized与Lock锁的区别

        (1)Synchronized 是Java的一个关键字,而Lock是java.util.concurrent.Locks 包下的一个接口;(2)Synchronized 使用过后,会自动释放锁,而Lock需要手动上锁、手动释放锁。(在 finally 块中)(3)Lock提供了更多的实现方法,而且 可响应中断、可定时, 而synchronized 关键字不能响应中断(4)synchronized关键字是非公平锁,即不能保证等待锁的那些线程们的顺序,而Lock的子类ReentrantLock默认是非公平锁,但是可通过一个布尔参数的构造方法实例化出一个公平锁;(5)synchronized无法判断,是否已经获取到锁,而Lock通过tryLock()方法可以判断,是否已获取到锁;(7)Lock可以通过分别定义读写锁提高多个线程读操作的效率。(8)二者的底层实现不一样:synchronized是同步阻塞,采用的是悲观并发策略;Lock是同步非阻塞,采用的是乐观并发策略(底层基于volatile关键字和CAS算法实现)

4、同步方法与同步块的区别

        区别:(1)同步方法默认用this或者当前类class对象作为锁;同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;(2)同步方法使用关键字 synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用   synchronized(object){代码内容}进行修饰;

5、什么是线程池?它的作用是什么?

        线程池,其实就是一个 容纳多个线程的容器 ,其中的线程可以反复使用,省去了频繁创建线程对象的操作 ,无需反复创建线程而消耗过多资源。

        作用:(1)减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。(2)可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

6、线程池有哪些核心参数?

        (1)int corePoolSize 核心线程最大数量。核心线程:线程池中有两类线程:核心线程和非核心线程。核心线程默认情况下会一直存在于线程池中,即使这个核心线程什么都不干,而非核心线程如果长时间的闲置,就会被销毁。

       (2)int maximumPoolSize 线程总数量最大值。该值等于核心线程数+非核心线程数。

       (3)long keepAliveTime 非核心线程的闲置超时时间。非核心线程闲置时间超过此值就会被销毁。

        (4)TimeUnit unit (keepAliveTime的单位)。TimeUnit是一个枚举类型NANOSECONDS : 1微毫秒,MICROSECONDS:1微秒,MILLSECONDS:1毫秒,SECONDS:1秒,MINUTES:1分,HOURS:1小时,DAYS:1天。

        (5)BlockingQueue workQueue 阻塞队列。常见的几个阻塞队列:LinkedBlockingQueue
:链式阻塞队列,默认大小:Interger.MAX_VALUE,可以指定大小。ArrayBlockingQueue:数组阻塞队列,需要指定大小。SynchronousQueue:同步队列,内部容量为0,每个put操作都必须等待一个take操作,反之亦然。DelayQueue:延迟队列,队列中的元素之后当其指定的延迟时间到了,才能从队列中获取到改元素。

        (6)ThreadFactory threadFactory 线程工厂:创建线程的工厂,用于批量创建线程,如果不指定,会新建一个默认的线程工厂。

        (7)拒绝处理策略,当无法创建新线程处理任务并且阻塞队列已满时就会采用拒绝处理策略。jdk默认四种策略:ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛出异常。ThreadPollExecutor.DiscardPolicy:丢弃新来的任务,但不抛出异常。ThreadPollExecutor.DiscardOldestPolicy:丢弃阻塞队列头部(最旧)的任务,然后重新尝试执行程序,(如果再次失败,重复此过程)。ThreadPollExecutor.CallerRunPolicy:由调用的线程去处理改任务。只适用于并发小的情况。

持续更新中,敬请期待!

参考文章:

并行和并发的区别_ch_atu的博客-CSDN博客_并发和并行的区别

多线程的锁升级原理 - 简书

https://www.csdn.net/tags/MtTaYg5sNDEwMDgtYmxvZwO0O0OO0O0O.html

 多线程高并发:synchronized锁升级过程及其实现原理 - 知乎

Synchronized锁和Lock锁的区别_Morning sunshine的博客-CSDN博客_synchronized和lock的区别

Synchronized与lock锁区别_yangdachaoa的博客-CSDN博客_lock锁和synchronized区别

synchronized与Lock的区别_灯下晚归人cc的博客-CSDN博客_线程锁synchronized和lock区别

Java面试题系列——JavaSE面试题(线程二)_循环网络不循环的博客-CSDN博客

什么是线程池,线程池的作用_王者生辉的博客-CSDN博客_线程池是什么

线程池概念和作用_小志的博客的博客-CSDN博客_线程池的作用线程池的七大核心参数以及常用的四种线程池_机智小袁的博客-CSDN博客_线程池七大核心参数线程池的7大核心参数是什么?

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小海海不怕困难

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值