本文选自【JAVA并发的艺术】一书的内容
1. 如何减少上下文切换?
减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。
· 无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一
些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。
· CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
·使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这
样会造成大量线程都处于等待状态。
· 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
2.引入多线程后,死锁现象如何避免?
· 避免一个线程同时获取多个锁。
·避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
·尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
3.线程可见性指的是什么?
可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当
的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。
4.内存屏障是什么?
是一组处理器指令,用于实现对内存操作的顺序限制。
5.缓存行是什么?
缓存中可以分配的最小单位
6.volatile变量修饰的共享变量进行写操作过程是?
将当前处理器缓存行的数据写回到系统内存。
这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
这种其实存在一种问题,就是其他处理器缓存的数据还是旧的。依旧是可以操作的。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当
处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
7.什么是CAS?
CAS(V,E,N)函数 V表示要更新的变量,E表示预期值,N表示新值。 如果V值等于E值,则将V的值设为N。若V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。
Unsafe方法提供了三个方法:
//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
以上存在什么问题?比如一个场景中一个线程A更新为B又更新为A。另一个线程认为这个值没有发生变化。那么如何解决这个问题呢?我们可以加上版本号来屏蔽这个问题。如果想共享多个变量如何做呢?jdk实现了AtomicReference类来保证引用对象之间的原子性。轻量级锁和互斥锁都使用了CAS原理。
8.什么是JAVA内存模型?
JAVA内存模型(JMM)主要讲的是内存到线程之间的工作流程。而JVM内存模型讲的是JVM的内部工作流程。
9.并发解决的核心问题是?
线程之间如何通信(共享内存和消息传递)及线程之间如何同步(这里的线程是指并发执行的活动实体)。
10。什么是线程安全? 当线程 访问 某个 方法 时 ,不管 你通过怎样的 调用方式 或者 这些 线程 如何交替 的 执行 ,这个类的结果行为 都是 我们 设想 的正确行为 ,那么 我们 就可以说这个类 是 线程 安全的 。
11 。什么是重排序?重排序分为哪几类?
重排序:重新安排语句的执行顺序。
重排序分为:编译器优化的重排序,指令级并行的重排序(指令级并行技术),内存系统的重排序(处理器使用缓存和读/写缓冲区)。
12.什么是happens-before?
happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见(很多人认为是执行过程可见性,这是错误的理解),那么这两个操作之间必须要存在happens-before关系。
规则如下:
程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
·监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
·volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
·传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
13.volatile读和写的内存语义是?
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量
·线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
·线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息。
·线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。
14.如何实现volatile的内存语义呢??
核心点在于如何来防止重排序,如果产生重排序那么结果就不对了。就不能做到线程安全了。如果能解决重排序也就解决了消息同步。
主要解决写写问题,读写问题,写读问题。
当一个线程存在前后写同一个变量的时候,不能够颠倒顺序前面的写不能跑到后面。这个在不同处理器呈现是不一样的,保险起见,那么引入屏障StoreStore这个概念在写之前插入该屏障。
当该线程第一个操作是写操作第二个是读操作是不能调换顺序的,后面的读不能跑到前面。这个基本在所有处理器中都有该问题,所以在写操作后面插入StoreLoad屏障。
当该线程中第一个操作是读,第二个操作也是读的时候是否和可以重排序呢?不能的,因为如果两个读可以重排表示不同时间点读到数据对结果是没有影响的。这个在不同处理器呈现是不一样的,保守会加上LoadLoad屏障。
当该线程中第一个操作是读,第二个操作也是写的时候是否和可以重排序呢?这个在不同处理器呈现是不一样的,保险起见,这时候在读操作后插入LoadStore屏障。
15.final的内存语义?
(1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用
变量,这两个操作之间不能重排序。
(2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能
重排序。