面试:多线程

java线程的6种状态:

1、初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2、运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3、阻塞(BLOCKED):表示线程阻塞于锁。
4、等待(WAITING):join,进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5、超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
6、终止(TERMINATED):表示该线程已经执行完毕。
 

1、并行和并发的区别

并行:同一时刻,多条指令在多个处理器上同时执行。无论从微观还是从宏观来看,二者都是一起执行

并发:同一时刻,只能有一条指令执行,多个指令被快速的轮换执行。宏观上具有同时执行的效果,但在微观上并不是同时执行,只是把时间分成若干段,使多个指令快速交替的执行

2、线程的几种状态:

新建状态:新创建一个线程对象

就绪状态:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

运行状态:就绪状态的线程获取了CPU,执行程序代码。

阻塞状态:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:

等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,

同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期

3、future原理

【转】FutureTask实现原理_男儿当自强-CSDN博客_futuretask 原理

4、对象的组成部分 Java并发编程 Synchronized及其实现原理 - 明耀 - 博客园

对象头、实例变量、填充

对象头包括:

mark word

类型指针 指向它的元数据,虚拟机通过这个指针来确定对象是哪个类的实例

数组长度

5、AQS   Java中的AQS_每天进步一点点-CSDN博客_aqs java

AQS即Abstract Queued Synchronizer(抽象队列同步器),用来实现各种锁,各种同步组件,它包含了state变量、加锁线程、等待队列等并发中的核心组件。我们常用的比如ReentrantLock,CountDownLatch等等基础类库都是基于AQS实现的。

通过构造一个基于阻塞的CLH队列容纳所有阻塞线程,而对该队列的所有操作通过CAS操作,但对已经获得锁的线程而言,Reentranlock实现了偏向锁

CLH(Craig Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配

6、lock和synchronized

Reentranlock底层就是AQS,默认使用非公平锁,首先CAS抢占锁,如果失败就调用LockSupport.park将当前线程阻塞,将其加入CLH队列;当持有锁的线程调用unlock,会将CLH队列头结点的下一个节点线程唤醒,调用LockSupport.unpark,唤醒线程从阻塞处继续循环,重新抢占锁,此时有可能被新线程抢占,导致重新进入阻塞状态,这就是非公平锁。

lock存储结构(同AQS):一个int类型状态值(用于锁状态变更);双向链表

synchronized  Java并发编程 Synchronized及其实现原理 - 明耀 - 博客园

利用对象头和monitor来实现

底层也是一个基于CAS操作的等待队列,把等待队列分为ContentionList和EntryList,目的是为了降低线程的出列速度

锁升级:

无锁->偏向锁->轻量级锁->重量级锁

锁降级:

当jvm进入安全点(SafePoint)的时候,会检查是否有闲置的monitor,然后试图降级

每日一面 - 什么是 Safepoint? - 简书SafePoint 每日一面 - 什么是 Safepoint? - 简书

JVM(三)----垃圾收集算法及Safe Point介绍 - 简书

Safepoint 可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,主动去检查,线程是否需要暂停,是否要做某些操作,如回收内存,锁降级等

synchronized同步方法和synchronized(this)使用的是同一把锁,锁对象。作用范围是同一个实例对象的方法或者代码块,作用对象同一个实例对象

synchronized同步静态方法和synchronized(class)使用的是同一把锁,锁类。作用范围是整个静态方法或者代码块,作用对象是这个类的所有对象

多线程访问synchronized(class)和synchronized(this),不会阻塞,因为锁的不是同一个对象,不是同一代码块,一个锁实例对象的代码块,一个锁类对象的代码块

区别:详解synchronized与Lock的区别与使用_brickworkers的博客-CSDN博客_synchronized与lock的区别

底层机制都是基于队列和一个状态值

都是可重入锁,可重入锁可以一定程度避免死锁:1.同一线程支持重入 2.父子类支持重入【内置锁重入】子类重写父类synchronized方法,父类方法锁住的对象是谁?_weixin_33670713的博客-CSDN博客

synchronized实现了自旋锁,并针对不同的系统和硬件体系进行了优化,而Lock则完全依靠系统阻塞挂起等待线程;Lock比synchronized更适合在应用层扩展,可以继承AbstractQueuedSynchronizer定义各种实现,比如实现读写锁(ReadWriteLock),公平或不公平锁;同时,Lock对应的Condition也比wait/notify要方便的多、灵活的多

synchronizedLock
Java关键字
无法获取锁状态可以获取tryLock()
自动释放锁手动释放锁unlock()
等待不可中断

可中断等待tryLock(long time, TimeUnit unit);

lockInterruptibly();

非公平可公平、非公平

锁代码块生成指令monitorenter、monitorexit;

锁方法通过判断运行时常量池ACC_SYNCHRONIZED标识

AQS

7、wait/notify、await/signal、LockSupport.park/unpark、sleep

8、ConcurrentHashMap如何保证线程安全? ConcurrentHashMap是如何实现线程安全的_|-| [- |_ |_ ()-CSDN博客_concurrenthashmap怎么实现线程安全

【67期】谈谈ConcurrentHashMap是如何保证线程安全的? - 知乎

java8实现锁分离,锁住一个node,锁住node之前是基于volatile和cas的无锁操作且线程安全

java8之前业务是锁分离,只不过锁住的是存储多个node的segment

get操作不会加锁,利用volatile保证每个node的可见性,且它的put,扩容,remove操作都设计的比较巧妙。相比hashTable,Collections.synchronizedMap()用一个大锁锁住整个hash列表性能要好很多

初始化线程安全:

多个线程同时进行put操作,在初始化数组时使用了乐观锁CAS操作来决定到底是哪个线程有资格进行初始化,其他线程均只能等待。

用到的并发技巧:

    volatile变量(sizeCtl):它是一个标记位,用来告诉其他线程这个坑位有没有人在,其线程间的可见性由volatile保证。
    CAS操作:CAS操作保证了设置sizeCtl标记位的原子性,保证了只有一个线程能设置成功

put操作线程安全:

由于其减小了锁的粒度,若Hash完美不冲突的情况下,可同时支持n个线程同时put操作,n为Node数组大小,在默认大小16下,可以支持最大同时16个线程无竞争同时操作且线程安全。当hash冲突严重时,Node链表越来越长,将导致严重的锁竞争,此时会进行扩容,将Node进行再散列,下面会介绍扩容的线程安全性。总结一下用到的并发技巧:

    减小锁粒度:将Node链表的头节点作为锁,若在默认大小16情况下,将有16把锁,大大减小了锁竞争(上下文切换),就像开头所说,将串行的部分最大化缩小,在理想情况下线程的put操作都为并行操作。同时直接锁住头节点,保证了线程安全
    Unsafe的getObjectVolatile方法:此方法确保获取到的值为最新

扩容操作线程安全:

ConcurrentHashMap运用各类CAS操作,将扩容操作的并发性能实现最大化,在扩容过程中,就算有线程调用get查询方法,也可以安全的查询数据,若有线程进行put操作,还会协助扩容,利用sizeCtl标记位和各种volatile变量进行CAS操作达到多线程之间的通信、协助,在迁移过程中只锁一个Node节点,即保证了线程安全,又提高了并发性能。

get操作线程安全:

对于get操作,其实没有线程安全的问题,只有可见性的问题,只需要确保get的数据是线程之间可见的即可

统计容器大小线程安全:

分两步:

利用CAS递增baseCount值来感知是否存在线程竞争,若竞争不大直接CAS递增baseCount值即可,性能与直接baseCount++差别不大

若存在线程竞争,则初始化计数桶,若此时初始化计数桶的过程中也存在竞争,多个线程同时初始化计数桶,则没有抢到初始化资格的线程直接尝试CAS递增baseCount值的方式完成计数,最大化利用了线程的并行。此时使用计数桶计数,分而治之的方式来计数,此时两个计数桶最大可提供两个线程同时计数,同时使用CAS操作来感知线程竞争,若两个桶情况下CAS操作还是频繁失败(失败3次),则直接扩容计数桶,变为4个计数桶,支持最大同时4个线程并发计数,以此类推…同时使用位运算和随机数的方式"负载均衡"一样的将线程计数请求接近均匀的落在各个计数桶中

9、volatile实现原理

volatile 变量使用条件丶Java教程网-IT开发者们的技术天堂

lock指令和内存屏障:禁止指令重排序,并非线程安全

典型的使用场景是作为标记使用,使用条件:

对变量的写操作不依赖于当前值;

该变量没有和别的volatile修饰的变量一起使用,一起在同一个式子中操作

10、线程

创建线程的4中方法:集成Thread类,实现Runnable、Callable接口,线程池

线程池特点:线程复用、控制最大并发数、管理线程、降低重复创建、销毁的消耗

11、CountDownLatch和CyclicBarrier

CyclicBarrier和CountDownLatch区别_泡泡鱼的专栏-CSDN博客_cyclicbarrier和countdownlatch区别

12、threadLocal内存泄漏

对ThreadLocal的一点理解_从不吹牛逼-CSDN博客

 13、如何实现一个线程安全的单例,前提是不能加锁

阿里面试官:如何实现一个线程安全的单例,前提是不能加锁 - 简书

java单例模式几种实现方式 - 点点积累 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值