java多线程知识点总结(面试利器)

1.什么是进程和线程

进程:程序运行资源分配的最小单位,进程内部有多个线程,会共享这个进程的资源

线程:CPU调度的最小单位,必须依赖进程而存在。

 

2. 新起线程方法

 

两种方法:继承类Thread

 

实现接口 Runnable

 

为什么要类,也要接口。因为JAVA单继承,类只能继承一个

 

接口 Callable  Runnable 区别:Callable 有返回值

 

3.怎么样才能让Java里的线程安全停止工作呢

 

线程自然终止:自然执行完或抛出未处理异常

 

stop()resume(),suspend()已不建议使用,stop()会导致线程不会正确释放资源,suspend()不释放资源容易导致死锁。

 

java线程是协作式,而非抢占式

 

调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。

 

isInterrupted() 判定当前线程是否处于中断状态。

 

static方法interrupted() 判定当前线程是否处于中断状态,同时中断标志位改为false

 

方法里如果抛出InterruptedException,线程的中断标志位会被复位成false,如果确实是需要中断线程,要求我们自己在catch语句块里再次调用interrupt()。

 

所有阻塞方法都会抛出InterruptedException

4.一个线程的生命周期(线程的方法)

新建线程调用start()方法进入就绪状态,cpu分配或join()方法获取执行权进入运行状态,调用sleep()wait()进入阻塞状态,sleep时间到或调用notifynotifyAll方法进入就绪状态,运行时还可以调用yield方法让步进入就绪状态进行重新争抢cpu

java多线程知识点总结(面试利器)

5.调用yield() sleep()wait()notify()等方法对锁有何影响? 

线程在执行yield()以后持有的锁是不释放的

sleep()方法被调用以后,持有的锁是不释放的

调动方法之前必须要持有锁调用了wait()方法以后,锁就会被释放,当wait方法返回的时候,线程会重新持有锁

调动方法之前必须要持有锁,调用notify()方法本身不会释放锁的

 

6.守护线程是什么

 

和主线程共死,finally不能保证一定执行

 

start之前setDaemon(true);设置守护线程,主线程运行完,子线程也结束。

 

7.synchronized内置锁

 

对象锁,锁的是类的对象实例。

 

类锁 ,锁的是每个类的的Class对象,每个类的的Class对象在一个虚拟机中只有一个,所以类锁也只有一个。

 

8.volatile关键字

 

适合于只有一个线程写,多个线程读的场景,因为它只能确保可见性。

 

9.ThreadLocal

   ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的变量值完成操作的场景。

   ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

   该类中方法有

 public T get() { }
 public void set(T value) { }
 public void remove() { }
 protected T initialValue() { }

    get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

线程变量。可以理解为是个map,类型 Map<Thread,T>

 

10.CountDownLatch

 

作用是一线程等待其他的线程完成工作以后在执行加强版join

await用来等待,countDown负责计数器的减一

 

11.CyclicBarrier

让一组线程达到某个屏障,被阻塞,一直到组内最后一个线程达到屏障时,屏障开放,所有被阻塞的线程会继续运行CyclicBarrier(int parties)

12.Semaphore

控制同时访问某个特定资源的线程数量,用在流量控制

13.Exchange

两个线程间的数据交换

14.CAS的原理

CAS(Compare And Swap),指令级别保证这是一个原子操作

基本思路:如果地址V上的值和期望的值A相等,就给地址V赋给新值B,如果不是,不做任何操作。

CAS可以有效的提升并发的效率,但同时也会引入ABA问题。

ABA问题可加入版本号进行标识是否有变更

    从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

 

15.Lock接口和synchronized的比较

synchronized jvm级别的锁,代码简洁,LockJava语言级别的锁,获取锁可以被中断,超时获取锁,尝试获取锁,读多写少用读写锁

16.可重入锁ReentrantLock所谓锁的公平和非公平有什么区别

如果在时间上,先对锁进行获取的请求,一定先被满足,这个锁就是公平的,不满足,就是非公平的

非公平的效率一般来讲更高

节约了从挂起(排队)到拿锁的时间

17.AQS是什么

AbstractQueuedSynchronizer

实质上用双向链表实现同步队列

用AQS实现同步,首先线程获取同步状态失败时,生成note节点加入同步队列尾部(用CAS设置),判断前驱是否为首节点,是的话尝试获取同步状态,获取成功将其设置为首节点,获取不成功进入等待队列,再去尝试获取同步状态

竞争失败的线程会打包成Node放到同步队列

注:AQS实质上拥有一个同步队列和多个等待队列,具体对应关系如下图所示:

java多线程知识点总结(面试利器)

上边为同步队列 双向链表
下边为condition等待队列 单向链表
Await从同步队列移动到等待队列等待
Signal从等待队列移动到同步队列争抢锁

18.Concurrenthashmap实现原理

1.7及之前: ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment实际继承自可重入锁(ReentrantLock),在ConcurrentHashMap里扮演锁的角色;HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,每个Segment里包含一个HashEntry数组,我们称之为table,每个HashEntry是一个链表结构的元素。

1.8

1、 取消了segment数组,直接用table保存数据,锁的粒度更小,减少并发冲突的概率。

2、 存储数据时采用了链表+红黑树的形式,纯链表的形式时间复杂度为O(n),红黑树则为Ologn),性能提升很大。什么时候链表转红黑树?当key值相等的元素形成的链表中元素个数超过8个的时候。

 

主要数据结构和关键变量

 

Node类存放实际的keyvalue,hash next

 

sizeCtl:

 

负数:表示进行初始化或者扩容,-1表示正在初始化,-N,表示有N-1个线程正在进行扩容

 

正数:0 表示还没有被初始化,>0的数,初始化或者是下一次进行扩容的阈值

 

TreeNode 用在红黑树,表示树的节点, TreeBin是实际放在table数组中的,代表了这个红黑树的根。

 

在高并发下的情况下如何保证取得的元素是最新的?

 

:用于存储键值对数据的HashEntry,在设计上它的成员变量value等都是volatile类型的,这样就保证别的线程对value值的修改,get方法可以马上看到。

 

ConcurrentHashMap如何在保证高并发下线程安全的同时实现了性能提升?

 

答:ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,只要多个修改操作发生在不同的段上,它们就可以并发进行。