学习笔记知识点专项(Java并发编程1)
1.上下文切换指什么?多线程一定快吗?为什么?
CPU通过时间分配算法来循环执行任务,当前线程执行一个时间片后会切换到下一个线程,但在切换前会保留一个任务的状态,以便下次切换回这个状态时,能够再加载这个任务的状态,所以任务从保存到再加载就是一次上下文切换。
因为线程有创建和上下文切换的开销。在并行执行操作不多的时候,多线程不一定好;当计算机资源限制比较严格的情况下,并行执行都会变为串行执行,这种情况下也是不适合。
2.如何减少上下文切换?
- 无锁并发编程:如将数据的ID按照按照hash算法取模分段,不同的线程处理不同段的数据
- CAS算法,java的atomic包使用CAS算法来更新数据,不需要加锁
- 使用最少线程,任务很少,线程很多,大多数线程都会处于等待状态,上下文切换也会增多
- 使用单线程:单线程内任务调度,维持多个任务之间的切换
3.死锁出现的场景?产生死锁的原因?
a.系统资源的竞争(不可剥夺资源的竞争才能产生死锁)
b.进程推进顺序非法(请求和释放资源的顺序不对)
4.产生死锁的必要条件(进程)
a.互斥条件:在一段时间内,某资源只能被一个进程占有
b.不剥夺:进程获得的资源在未使用完毕之前,不能被其他进程强行夺走
c.请求和保持条件:进程已经保持了至少一个资源,但又提出新的资源请求,而资源被其他进程占有,则请求进程被阻塞,而自己对已经获得的资源保持不放
d.循环等待条件:存在一种进程的资源的循环等待链,链中每个进程已经获得了一个资源,但是同时这个资源被链中的下一个进程锁请求
5.死锁的处理策略
a.预防死锁:破坏产生死锁四个必要条件中的一个或几个,以防止发生死锁(保守,宁可资源闲置),一次请求所有资源,资源剥夺,资源按顺序分配
b.避免死锁:在资源的动态分配过程中,用某种方法防止系统进入不安全的状态,从而避免死锁,折中,在运行时判断是否可能死锁,寻找可能的安全允许顺序,银行家算法,不必剥夺但是必须知道将来的资源需求
c.死锁检测:允许进程在运行过程中发生死锁,通过系统检测机构及时地检测出死锁的发生,然后才去某种措施解除死锁。允许就分配资源,定期检查死锁是否已经发生。通过剥夺解除死锁,造成损失。
6.如果避免死锁的几种方法
a.避免一个线程同时获得多个锁
b.避免一个线程在锁内占用多个资源,尽量保证每个锁只占用一个资源
c.尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
d.对于数据库锁,加锁和解锁必须在数据库连接里,否则会出现解锁失败的现象
7.什么是资源限制?资源限制引发的问题?如何解决资源限制问题?如何在资源限制的条件下进行并发编程?
a.在进行并发编程时,程序的执行速度受限于计算机硬件资源和软件资源
b.在并发编程中,将代码执行速度加快的原因就是将串行变成了并发执行,资源受限导致仍串行执行+上下文切换和资源调度的时间
c.硬件-->集群并行执行程序|| 软件-->使用资源池将资源复用
d.根据不同的资源调整程序并发数
8.关于volatile的理解?它的实现原理?如何进行优化?
定义:java编程语言允许线程访问共享变量,为了确保这个共享变量能被准确和一致地更新,线程应该确保通过排它锁单独获取这个变量。
实现原理:volatile修饰的共享变量在进行写操作时,调用lock前缀指令:将当前处理处理器缓存行的数据写回系统内存,这个写回内存的操作会使其他cpu中缓存该内存地址的数据无效
优化:追加字节优化性能:队列的入队和出队时需要不停地改变头结点和尾结点,追加字节能够避免头尾节点加到同一个缓存行,使头尾节点修改时不会相互锁定。
volatile是一种较弱的同步机制,可以解决共享变量的可见性问题。如果变量被声明为volatile,那么编译期和JVM会把变量当做共享变量并禁止对变量的一些重排序操作,修改变量后立刻对所有线程可见。但volatile无法解决原子性问题和一致性问题。
9.synchronized实现同步的基础,有哪几种表现形式?synchronized在JVM中实现原理?
JVM进入和退出monitor对象来实现同步和代码块的同步,代码块同步和方法同步有所不用。Monitorenter指令是在编译后插入到同步代码的开始位置,monitorexit是插入到方法的结束和异常处,它们可以实现synchronized的可重入性。
10.锁的状态有几种?偏向锁的目的和实现原理?各种锁的适用场景和优缺点?
偏向锁
- 大多数情况下,锁不仅不存在竞争,而是总是由同一条线程多次获得,为了使线程获得锁的代价更低,而引入偏向锁,当一个线程访问同步代码块获得锁时,会在对象头和栈帧中的锁记录里记录锁偏向的线程ID,以后该锁在进入和退出同步代码块时不需要进行CAS操作来加锁和解锁。在出现锁竞争后,偏向锁释放,转变为轻量锁。
- 加锁和解锁不需要额外的消耗,如果线程间存在锁竞争,会带来额外的锁撤销的消耗,适用于一个线程访问同步代码块的情形
轻量级锁
- 竞争的线程不会阻塞,提高程序的响应速度,如果始终得不到锁竞争的线程,使用自旋会消耗CPU,追求响应时间,同步块执行速度快
重量级锁:
线程竞争不使用自旋,不会消耗CPU,线程阻塞,响应时间缓慢,追求吞吐量,同步块执行速度较长
11.原子操作的实现原理?
第一个机制是使用总线锁保证原子性
使用缓存锁保证原子性
12.java如何实现原子操作
原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段。
通过锁或循环CAS来实现原子操作
会出现的问题:
(1)ABA问题:使用AtomicStampedReference来解决
(2)循环开销时间大
(3)只能保证一个共享变量的原子操作,使用AtomicReference类保证引用对象之间的原子性
除了偏向锁,当一个线程想要进入同步代码块时使用循环CAS来获取锁,当它想退出同步代码块的时候使用循环CAS释放锁。
13.并发编程中两大关键问题,线程之间如何通信,线程之间如何同步
线程之间的通信机制有共享内存和消息传递
共享内存模型中,同步是显式进行的,而消息传递模型中,同步是隐式进行的。
14.从线程A到线程B通信的步骤
线程A把本地内存A更新过的共享变量刷新到主内存中
线程B通过主内存把线程A更新后的共享变量读入线程B的的本地内存中
15.java代码的指令重排序
编译器优化重排序,指令集并行重排序,内存系统重排序,通过内存屏障来保证指令序列的一定程度上有序
16.指令重排序
在单线程看来,并没有什么影响,但在多线程程序中,可能产生不一样的结果
17.顺序一致性
如果程序是正确同步的,程序的执行将具有顺序一致性,即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。
两大特征:
一个线程中的所有操作必须按照程序的顺序来执行
不管程序同步与否,所有线程都只能看到一个单一的操作执行顺序,在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
但是JMM模型并没有保证顺序一致性,未同步的程序在JMM中部但整体的执行顺序是无序的,而且所有线程看到的操作执行顺序也可能不一致。未同步的程序在JMM中执行时,整体上是无序的,其执行结果无法预知。
18.volatile内存语义
a.当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存总
b.当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来从主内存中读取共享变量
c.volatile禁止重排序,实质上是利用在volatile的读和写的前后插入内存屏障来实现的。
19.锁的内存语义
a.当线程释放锁,JMM会把线程对应的本地内存中的共享变量刷新到主内存中
b.当线程获得锁,JMM会把该线程对应的本地内存置为无效,从而保证临界区代码必须从主内存中读取共享变量
从这里可以看出 锁的获取和释放和volatile的读和写具有相同语义
20.fina的内存语义
a.写final域之前一定要在final域初始化之后
b.读final域之前,一定先读包含final域的对象的引用
final语义必须保证final引用不能从构造函数中逃逸,否则可能读到未初始化的值
(然而逃逸啥意思。。。)
21.happens-before是jmm的核心的概念
happens-before是jmm最核心的概念,出发点是保证程序员足够强的内存可见性保证。
(331页)
22.请写出线程安全的单例模式,为什么需要双重检查?为什么实例要加volatile。其他线程安全的方案,区别
23.java内存模型综述
24.什么是线程
现代操作系统调度的最小单元是线程,也叫轻量级进程
为什么要使用线程?更多的处理器核心;更快的响应速度;更好的编程模型
线程的优先级?通过一个整型变量priority来控制优先级,优先级的范围是1-10
25.线程的状态
new初始状态,线程被构建,但是还没有调用start()方法
runnable运行状态=就绪or运行
blocked阻塞状态
waited等待状态,表示当前线程需要等待其他线程作出一些特定的动作
time_waiting指定时间自行返回
terminated终止状态,表示该线程已经结束,执行完run()之后
26.daemon线程是什么?
27.启动和终止线程的详细介绍
28.线程间的通信
a.volatile和synchronized关键字:确保所有线程对变量的可见性,确保所有线程对变量的可见性和排他性
b.任意对象对Object的访问(由synchronized保护),首先要获得object的监视器,如果获取失败,线程进入同步队列,线程变为blocked,当访问object的前驱释放了锁,则该释放锁的线程唤醒阻塞在同步队列中的线程,使其尝试重新对监视器的获取
c.等待/通知机制(对应lock的condition)
1.使用wait(),notify(),notifyAll()时需要先对调用对象加锁
2.调用wait方法之后,线程状态由running变为waiting,并将当前线程放置到等待队列中
3.notify(),notifAll方法调用后,等待线程仍然不会从wait方法中返回,需要等调用前面方法的线程释放锁之后,等待线程才有机会wait返回
4.notity,notifyAll的区别,进行方法后由waiting状态变为blocked状态
5.从wait方法返回的前提是获得了调用对象的锁
d.等待/通知的经典范式
等待方:
1.获取对象的锁
2.如果条件不满足,那么调用wait方法,被通知后仍要检查条件
3.条件满足,执行对应的逻辑
通知方:
1.获取对象的锁
2.改变条件
3.通知所有等待在对象上的线程
e.管道输入/输出流 pipedOutputStream,pipedinputstream,pipedReader,PipedWriter
f.Thread.join()的应用:如果一个线程A执行了线程b.join() 当前a等待b线程终止后才能从b.join返回
g.ThreadLocal的使用,线程变量,以threadLocal(threadID)对象为键,任意对象为值的存储结构。一个线程可以根据一个ThreadLocal对象查询到绑定在该线程上的值。
29.线程应用实例
30.java中如何停止一个线程
jdk1.0本来有一些像stop,suspend,resume的控制方法大那是由于潜在的死锁威胁因此在后续的jdk版本中都被弃用了。之后当run()或者call()方法执行完线程会自动结束,如果要手动结束一个线程,可以用volatile布尔变量来退出run方法的循环或者是取消任务来中断线程。
31.一个线程运行时发生异常会怎样
32.如何在两个线程间共享数据
可以通过共享对象实现这个目的,或者使用像阻塞队列这样的并发的数据结构。
33.为什么wait,notify,notifyAll这些方法不在thread类里面
当一个线程需要调用对象的wait方法时,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁,由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。