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时间到或调用notify,notifyAll方法进入就绪状态,运行时还可以调用yield方法让步进入就绪状态进行重新争抢cpu
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级别的锁,代码简洁,Lock:Java语言级别的锁,获取锁可以被中断,超时获取锁,尝试获取锁,读多写少用读写锁
16.可重入锁ReentrantLock中所谓锁的公平和非公平有什么区别
如果在时间上,先对锁进行获取的请求,一定先被满足,这个锁就是公平的,不满足,就是非公平的
非公平的效率一般来讲更高
节约了从挂起(排队)到拿锁的时间
17.AQS是什么
AbstractQueuedSynchronizer
实质上用双向链表实现同步队列
用AQS实现同步,首先线程获取同步状态失败时,生成note节点加入同步队列尾部(用CAS设置),判断前驱是否为首节点,是的话尝试获取同步状态,获取成功将其设置为首节点,获取不成功进入等待队列,再去尝试获取同步状态
竞争失败的线程会打包成Node放到同步队列
注:AQS实质上拥有一个同步队列和多个等待队列,具体对应关系如下图所示:
上边为同步队列 双向链表
下边为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),红黑树则为O(logn),性能提升很大。什么时候链表转红黑树?当key值相等的元素形成的链表中元素个数超过8个的时候。
主要数据结构和关键变量
Node类存放实际的key和value值,hash ,next
sizeCtl:
负数:表示进行初始化或者扩容,-1表示正在初始化,-N,表示有N-1个线程正在进行扩容
正数:0 表示还没有被初始化,>0的数,初始化或者是下一次进行扩容的阈值
TreeNode 用在红黑树,表示树的节点, TreeBin是实际放在table数组中的,代表了这个红黑树的根。
在高并发下的情况下如何保证取得的元素是最新的?
答:用于存储键值对数据的HashEntry,在设计上它的成员变量value等都是volatile类型的,这样就保证别的线程对value值的修改,get方法可以马上看到。
问ConcurrentHashMap如何在保证高并发下线程安全的同时实现了性能提升?
答:ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,只要多个修改操作发生在不同的段上,它们就可以并发进行。