进程和线程的区别?
- 进程是一个‘执行中的程序’,是系统进行资源分配和调度的一个独立单位
- 线程是进程的一个实体,一个进程一般拥有多个线程。线程之间共享地址空间和其他资源(所以通讯和同步等操作,线程比进程更容易)
- 线程一般不拥有系统资源,但是也有一些必不可少的资源(使用ThreadLocal存储)
- 线程的上下文切换比进程的上下文切换要快得多<如何理解这句话?>
- 进程切换时,涉及到当前进程CPU环境的保存和新被调度运行进程的CPU环境的设置
- 线程切换时,仅需要保存和设置少量寄存器内容,不涉及存储管理方面的操作。
线程可以拥有属于自己独立的资源嘛?
答:可以的,通过ThreadLocal可以存储线程的特有对象,也就是属于当前线程的资源。
进程之间常见的通讯方式?
通过使用套接字Socket来实现不同机器间的进程通讯
通过映射一段可以被多个进程访问的共享内存来进行通讯
通过写进程和读进程利用管道进行通讯
线程的状态有哪些?
- NEW
- RUNNABLE
- BLOCKED
- WAITING
- TIMED_WAITING
- TERMINATED
sleep和wait的区别?
sleep方法是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态,当睡眠时间到了,会解除阻塞,进入可运行状态,等待CPU的到来。睡眠不释放锁
wait方法是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify或者notifyall被调用后,会解除阻塞。但是,置于重新占用互斥锁之后才能进入可运行状态。睡眠时,会释放互斥锁。
join和yield方法?
join方法:当前线程调用,则其他线程全部停止,等待当前线程执行完毕,接着执行。
yield方法:该方法是的线程放弃当前分到的CPU时间,但是不是线程阻塞,即线程扔处于可执行状态,随时可能再次分的CPU时间。
线程死锁的问题:死锁是最常见的一种线程活性故障。死锁的起因是多个线程之间相互等待对方而被永远暂停(处于非Runnable)。死锁产生的四个必要条件。
- 资源互斥:一个资源每次只能被一个线程使用
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:线程已经获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
如何避免死锁的发生?
- 粗锁法:使用一个粗粒度的锁来消除“请求与保持条件”,缺点是会明显降低程序的并发性能会导致资源的浪费
- 锁指定法:(必须回答出来的点)指定获取锁的顺序,比如某个线程只能获得A锁和B锁,才能对某个资源进行操作,在多线程条件下,如何避免死锁? - ->通过指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获得B锁,按顺序获取锁就可以避免死锁。这通常被认为是解决死锁很好的一种方式。
- 使用显式锁中的ReentrantLock.try(Long,Timeunit)来申请锁。
谈一谈对Synchronized关键字的理解
答:synchronized是Java的一个关键字,是一个内部锁。他可以使用在方法上和方法块上。表示同步方法和同步方法块。在多线程环境下,同步方法或者同步代码块在同一时刻只允许有一个线程在执行,其余的线程都在等待获取锁,也就是实现了整体并发中的局部串行。
谈一谈内部锁底层的实现?
- 进入时,执行monitorEnter,将计数器+1,释放锁monitorExit时,计数器-1
- 当一个线程判断到计数器为0时,则当前锁空闲,可以占用。反之,当前线程进入等待状态。
谈一谈你对Volatile关键字的理解?
答:volatile关键字是一个轻量级的锁,可以保证可见性和有序性,但是不保证原子性
解析:
- volatile可以保证主内存和工作内存直接产生交互,进行读写操作,保证可见性
- volatile技能保证变量写操作的原子性,不可以保证读操作的原子性
- volatile可以禁止指令重排序(通过插入内存屏障),典型的案例是在单例模式中使用
ReentrantLock和synchronized的区别?
答:synchronized 是可重入的排它锁,和 ReentrantLock 锁功能相似,任何使用 synchronized 的地方,几乎都可以使用 ReentrantLock 来代替,两者最大的相似点就是:可重入 + 排它锁,两者的区别主要有这些:
- ReentrantLock 的功能更加丰富,比如提供了 Condition,可以打断的加锁 API、能满足锁 + 队列的复杂场景等等;
- ReentrantLock 有公平锁和非公平锁之分,而 synchronized 都是非公平锁;
- 两者的使用姿势也不同,ReentrantLock 需要申明,有加锁和释放锁的 API,而 synchronized 会自动对代码块进行加锁释放锁的操作,synchronized 使用起来更加方便。
谈一谈对CAS算法的理解?
答:CAS是Compare and swap的缩写,翻译过来就是比较替换。其实CAS是乐观锁的一种实现。而Synchronized则是悲观锁。这这里的乐观和悲观指的是当前线程对是否有并发的判断。CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
悲观锁——认为每一次自己的操作大概率会有其他线程在并发,所以自己在操作前都有对资源进行锁定,这种锁定是排他的。悲观锁的缺点是不但把多线程并行转成了串行,而且枷锁和释放锁都有额外的开支。
乐观锁——认为每一次操作时大概率不会有其他线程并发,所以操作时并不枷锁,而是对数据操作时比较数据的版本,和自己更新前取得的版本一致才进行更新。乐观锁省掉了加锁、释放锁的资源消耗,而且在并发量并不是很大的时候,很少会发生版本不一致的情况,此时乐观锁效率会更高。
Atomic变量的操作是如何保证原子性的,其实就是使用了CAS算法。
Atomic 变量在做原子性操作时,会从内存中取得要被更新的变量值,并且和你期望的值进行比较,期望的值则是你要更新操作的值。如果两个值相等,那么说明没有其它线程对其更新,本线程可以继续执行。如果不等,说明有线程已经先于此线程进行了更新操作。那么则继续取得该变量的最新值,重复之前的逻辑,直至操作成功。这保证了每个线程对 Atomic 变量操作是线程安全的
CAS的缺点?
循环时间长开销很大;
- 如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,可以使用循环CAS的方式保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这时候就可以用锁来保证原子性。
ABA问题。如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?
如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。
JVM对资源进行调度方式分为公平调度和非公平调度。
公平调度方式:按照申请的先后顺序授权资源的独占权
非公平调度方式:新来的线程(活跃线程)可以先被授予该资源的独占权。