多线程
1.什么是线程和进程? 线程与进程的关系,区别及优缺点?
进程是系统资源分配和调度的基本单位,线程是cpu资源分配和调度的基本单位,
一个程序至少有一个进程,一个进程至少有一个线程。
进程有独立的内存单元,线程共享进程的内存
2.进程间的通信方式
管道以及命名管道:管道用于具有父子关系的进程通信,有名管道无论两个进程间有没有关系,都可以通信。
信号:用于通知接收进程某个事情已经发生。
消息队列:消息的链接表,克服了上面两种方式中信号量有限的缺点,对消息队列具有写权限的进程可以按照一定的规则向消息队列中添加消息,对消息队列具有读权限的进程可以从消息队列中读取信息。
共享内存:进程间最有用的通信方式,需要同步方式,比如互斥锁和信号量等。
信号量:进程间或同一进程不同线程间同步和互斥的手段。
套接字:应用于网络中的一般进程间的通信方式。
2.说说并发与并行的区别?
并发和并行从宏观上来讲都是同时处理多路请求的概念。
但并发和并行又有区别,并行是指两个或者多个事件在同一时刻发生;
而并发是指两个或多个事件在同一时间间隔内发生
4.使用多线程可能带来什么问题?
(内存泄漏、死锁、线程不安全等等)
对于共享数据的脏读,使用volatile关键字可以解决。
让共享数据在多线程中可见,防止指令重排序
5.创建线程有哪几种方式?
①继承thread类②实现runnable接口③实现callable接口④使用线程池
6.说说线程的生命周期和状态?
线程的生命周期经过「新建(New)、就绪(Runnable)、运行(Running)、阻塞(Bolocked)和死亡(Dead)」
8.什么是线程死锁?如何避免死锁?
多个线程相互等待对方而处于暂停状态
死锁情况:
解除死锁:
因为synchronize块执行完就会释放锁
①使用一个粒度粗的锁,来消除请求与保持条件,缺点是降低并发性能,消耗资源
②锁排序法,指定锁获取的顺序
9.说说 sleep() 方法和 wait() 方法区别和共同点?
相同:都会使线程处于阻塞状态
不同:sleep是thread类的方法,睡眠不释放锁,时间一到继续运行
wait是objectt的方法,必须与synchronized关键字一起使用,睡眠释放锁,当notify或notifyall时,需要重新占用锁才能运行
10.为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
首先通过对象.run()方法可以执行方法,但是不是使用的多线程的方式,就是一个普通的方法,要想实现多线程的方式,一定需要通过对象.start()方法。
start() 方法调用后,该线程并不一定会立马执行,只是将线程变成了可运行状态(NEW —> RUNNABLE)。具体什么时候执行,取决于 CPU ,由 CPU 统一调度。
12线程中的常见方法
sleep方法:是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态。睡眠时间结束,会解除阻塞,进入可运行状态,等待CPU的到来。睡眠不释放锁。
wait方法:是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify或者notifyall被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。睡眠时,会释放互斥锁。
join 方法:当前线程调用,则其它线程全部停止,等待当前线程执行完毕,接着执行。
yield 方法:该方法使得线程放弃当前分得的 CPU 时间。但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。
线程优先级
守护线程
13.线程的活性故障有哪些?
答:常见的线程活性故障包括死锁,锁死,活锁与线程饥饿。
(1)线程死锁:
死锁是最常见的一种线程活性故障。死锁的起因是多个线程之间相互等待对方而被永远暂停(处于非Runnable)。死锁的产生必须满足如下四个必要条件:
资源互斥条件:一个资源每次只能被一个线程使用。
请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:线程已经获得的资源,在未使用完之前,不能强行剥夺。
循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
如何避免死锁的发生?
粗锁法:使用一个粒度粗的锁来消除“请求与保持条件”,缺点是会明显降低程序的并发性能并且会导致资源的浪费。
锁排序法:指定获取锁的顺序
14.多线程下的线程安全
答:多线程环境下的线程安全主要体现在原子性,可见性与有序性方面。
原子性 : 一个的操作或者多次操作,要么所有的操作全部都得到执行并且不会收到任何因素的干扰而中断,要么所有的操作都执行,要么都不执行。synchronized 可以保证代码片段的原子性。
可见性 :当一个变量对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。volatile 关键字可以保证共享变量的可见性。
有序性 :代码在执行的过程中的先后顺序,Java 在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。volatile 关键字可以禁止指令进行重排序优化。
15.volatile关键字
保证变量在多线程之间的可见性,禁止指令重排序,保证内存有序性
16.谈谈你对synchronized关键字的理解
答:1.synchronized是Java中的一个关键字,可以保证原子性。它可以使用在方法和方法块上,表示同步方法和同步代码块。用在方法上同步的本身,可以在方法块内同步别的object,在多线程环境下,同步方法或者同步代码块在同一时刻只允许有一个线程在执行,其余线程都在等待获取锁,也就是实现了整体并发中的局部串行。
注意!面试时经常会问比较synchronized和volatile,它们俩特性上最大的区别就在于原子性,volatile不具备原子性。
synchronized底层实现:
锁被占用时,执行monitor.enter,将计数器+1,释放锁monitor.exit时,计数器-1
当一个线程判断到计数器为0时,则当前锁空闲,可以占用;反之,当前线程进入等待状态
2.早期的synchronized
JDK1.6之前属于重量级锁,依赖于操作系统,比较耗时,效率底下。
3.对synchronized的优化
JDK1.6之后在JVM层面对synchronized底层做了很多的优化,包括偏向锁,轻量级锁,自旋锁,自适应自旋锁,锁消除,锁粗化等优化技术。
Lock(ReentrantLock)锁与synchronize区别
ThreadLocal
1.有啥用?
ThreadLocal主要为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
2.内存泄露问题
ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。
3.解决方案
每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
就算ThreadLocalMap的key使用强引用,也会出现内存泄漏的情况
ThreadLocalMap解决哈希冲突
ThreadLocal与synchronized的区别
ConcurrentHashMap和Hashtable的区别?
答: ConcurrentHashMap结合了HashMap和Hashtable二者的优势。HashMap没有考虑同步,Hashtable考虑了同步的问题。但是Hashtable在每次同步执行时都要锁住整个结构。
ConcurrentHashMap将hash表分为16个桶,诸如get、put、remove等常用操作只锁上当前需要用到的桶。
ConcurrentHashMap的具体实现方式(分段锁):
该类包含两个静态内部类MapEntry和Segment,前者用来封装映射表的键值对,后者用来充当锁的角色。
Segment是一种可重入的锁ReentrantLock,每个Segment守护一个HashEntry数组里得元素,当对HashEntry数组的数据进行修改时,必须首先获得对应的Segment锁。
线程池
拒绝策略
CallerRunsPolicy(调用者运行策略)
AbortPolicy(中止策略)线程池默认的拒绝策略(不值当就是它),拒绝任务时,直接抛出一个运行时异常。你可以捕获逐个异常并根据自己的业务进行处理。
DiscardPolicy(丢弃策略)拒绝任务时不通知你
DiscardOldestPolicy(弃老策略)
1.为什么要用线程池?
①.降低资源消耗:
②.提高响应速度:
③.提高线程的可管理性:
2.如何创建线程池比较好?(推荐使用 ThreadPoolExecutor 构造函数创建线程池)
为什么不推荐Executors直接创建线程池.阿里不让。
Executors创建出来的线程池使用的全都是无界队列,而使用无界队列会带来很多弊端,最重要的就是,它可以无限保存任务,因此很有可能造成异常。
ThreadPoolExecutor 部分参数:
线程池提交runnable接口
是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个接口或类
3.线程池原理了解吗?
java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。
4.几种常见的线程池了解吗?
缓存型池子 调度型线程池
5.如何设置线程池的大小?
(其中 N 代表 CPU 的个数)
CPU 密集型应用,线程池大小设置为 N + 1
IO 密集型应用,线程池大小设置为 2N
CountDownLatch 是等待一组线程执行完,才执行后面的代码。此时这组线程已经执行完。
想让线程停止,定义一个标识位 boolean flag=true;
模拟倒计时
生产者消费者
锁
锁的常见分类
1.可重入锁和非可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁,不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
非可重入锁,外层方法获取锁以后因为没有释放锁进入内层方法就会出现阻塞。
2.公平锁与非公平锁
如果一个线程组里,能保证每个线程都能拿到锁,那么这个锁就是公平锁。相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。
3.数据库锁了解吗?
答:MySQL 数据库的锁分为表级锁和行级锁。从数据库角度看,行级锁又可以分为独占锁和共享锁。
独占锁,独占锁锁定的资源只允许进行锁定操作的程序使用, 其它任何对它的操作均不会被接受。
共享锁,其锁定的资源可以被其他用户读取,但不能被修改。
4.悲观锁和乐观锁
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized等独占锁就是悲观锁思想的实现。
乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。
CAS
比较与交换(compare and swap),是一种无锁算法,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)