java面试题:多线程与并发

时间:2023-01-25 06:26:13

多线程

关键词:线程池

Q:如何新建一个线程?
继承Thread,或者实现Runnable接口,或者通过Callable接口实现。
Q:Callable怎么用?
Callable可以作为FutureTask的方法参数。
FutureTask进行多线程操作时,还可以返回一个结果,也就是通过FutureTask实现异步。
Q:线程有哪些状态?
新建,就绪,运行,阻塞,停止
阻塞可以是sleep(),wait(),或者join()
Q: sleep() 和 wait() 的区别?
所属的类不一样。Thread.sleep(1000); 而wait()是属于Object的。
sleep()不会释放锁,而wait()会释放锁。
Q:死锁是怎么回事?
死锁,就是两个(或多个)线程对彼此加锁的资源进行加锁,导致彼此等待而永远阻塞。
比如,如果线程1锁住了A,然后尝试对B进行加锁,同时线程2已经锁住了B,接着尝试对A进行加锁,这时死锁就发生了。线程1永远得不到B,线程2也永远得不到A,并且它们永远也不会知道发生了这样的事情。为了得到彼此的对象(A和B),它们将永远阻塞下去。这种情况就是一个死锁。
Q:如何避免死锁?如何解决死锁?
破坏“不可剥夺”条件:一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
破坏”请求与保持条件“:第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源。第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。
破坏“循环等待”条件:采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程。

Q:在实践中,有没有使用过多线程?
可以使用多线程同时执行多个任务,提高程序运行的效率。
还可以通过异步来处理耗时操作。比如一个线程做为主线程,新建另一个线程进行下载任务,这样主线程就不需要等待耗时操作。
Q:有三个线程,如何使它们顺序执行?
使用join,可以让某个线程在另一线程之前执行。
Q:假如有Thread1、Thread2、Thread3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?
使用ForkJoinPool 。可以进行fork()分而治之,将任务进行分解,然后合并所有的结果。
Q:如何中断线程?中断线程意味着什么?
interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。
如果线程被wait, join和sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。
如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException。
Q:线程池有没有了解过?为什么要用线程池?
新建线程的开销太大了,使用线程池可以节省系统资源。
Q:线程池的参数有哪些?

  • 参数如下:
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
  • corePoolSize:核心线程池里面的线程数量
  • maximumPoolSize:线程池允许的最大线程数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime:表示当线程池的个数大于核心数量corePoolSize时,线程的空闲时间达到指定的时间时会被销毁。
  • unit:参数keepAliveTime的时间单位
  • workQueue:一个阻塞队列,用来存储等待执行的任务。当请求的线程数大于corePoolSize时,线程会进入这个BlockingQueue阻塞队列。
  • handler:执行拒绝策略的对象。当阻塞队列workQueue的任务缓存区到达上限,并且活动的线程数大于maximumPoolSize的时候,线程池会执行拒绝策略。
  • threadFactory: 定义如何启动一个线程,可以设置线程的名称,并且可以确定是否是后台线程等。

Q:拒绝策略有哪些?
拒绝策略有以下几种:
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。 (默认)
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中等待最久的任务,然后把当前任务加入队列。
ThreadPoolExecutor.CallerRunsPolicy:由调用任务的run()方法绕过线程池执行此线程。
Q:阻塞队列有哪些?
Q:阻塞队列的默认值是多少?
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列
LinkedBlockingQueue:一个基于链表结构的阻塞队列。LinkedBlockingQueue如果不指定大小,就是一个*队列。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
Q:线程池有哪些类型?有什么不同?
1.newCachedThreadPool:
new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);
newCachedThreadPool里面的corePoolSize核心线程数为0,最大线程数设置最大。它很灵活,但是要注意线程数量不要太大影响性能。

2.newFixedThreadPool:
ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
FixedThreadPool是 具有线程池提高程序效率和节省创建线程时所耗的开销的优点。
但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

3.newSingleThreadExecutor:
new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory));
newSingleThreadExecutor只允许一个线程。

4.newScheduledThreadPool:
newSingleThreadExecutor可以延迟执行。
Q:怎么手动实现一个线程池?

并发基础

关键词:线程安全、synchronized同步锁、Lock锁、volatile可见性、AtomicInteger原子操作类、CAS、AQS、ThreadLocal

Q:线程安全是什么?
多个线程操作同一共享变量时,需要保证数据的安全性。
Q:同步有哪些?
synchronized关键字和Lock锁。
Q:synchronized的底层实现?
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
详情参见: https://www.cnblogs.com/paddix/p/5367116.html
Q:锁有哪些?
可重入锁ReentrantLock。可一个线程获得某个对象的方法的锁时,想要访问该对象的其他方法时无需再加锁。
ReentrantLock还可以设置为公平锁还是非公平锁。公平锁,会保证各个线程尽可能公平地拿到锁,不会导致某个线程一直拿不到锁的情况。
Q:ReentrantReadWriteLock是什么?读的时候可以写吗?读的时候可以读吗?写的时候可以写吗?
可重入的读写锁。读写锁分为读锁和写锁。
“读读共存,写写不共存,读写不共存”。
https://www.cnblogs.com/expiator/p/9374598.html
Q:AQS有没有了解过
AbstractQueueSynchronizer。抽象队列同步器。
CountDownLatch、Semaphore、Carrier这些并发工具类都是基于AQS实现的。线程池的内部也有继承自AQS的类。
Q:讲一下并发工具类是怎么使用的?
CountDownLatch是闭锁(阀门)。 更具体的待补充。。
Semaphore是信号量。
Carrier是 栅栏。
Q:有没有了解过CAS?
CAS就是Compare And Swap,比较和替换。
比如说,比较当前状态是否为开启状态,如果不是开启状态,就将其改为开启状态。
需要读写的内存值: V,进行比较的预估值: A,拟写入的更新值: B。
当且仅当 V == A 时, V = B。
Q:CAS原理是什么?
比较和替换。相当于有一个版本号,运用了乐观锁的机制。
Q:volatile关键字有什么用?
可以保证线程的可见性,线程修改共享变量时会被其他线程发现。还可以禁止指令重排序。
Q:volatile是怎么实现的?
这个涉及到Java内存模型。JMM。
详情见: https://www.cnblogs.com/xrq730/p/7048693.html
Q:volatile能保证线程安全吗?
不能。volatile只有进行原子操作时,才是线程安全的
Q:使用volatile操作i++,是否线程安全?如果不安全,应该怎么处理?
线程不安全。因为i++不是原子操作。
可以使用AtomicInteger原子操作类进行操作。
Q:volatile和全局变量有什么区别?
Q:AtomicInteger是怎么实现的?
CAS乐观锁机制。比较和替换实现。
Q:讲一下ThreadLocal
每个线程都有一个自己的副本变量。
Q:讲一下ThreadLocal的底层实现。
Q:ThreadLocal是如何为每个线程创建变量的副本的?
Q:ThreadLocal是如何做到在不同线程set()、get()的值不被其它线程访问的;
各线程对共享的ThreadLocal实例进行操作,实际上是以该实例为键对内部持有的ThreadLocalMap对象进行操作。
首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

Q:ThreadLocal可能会导致哪些问题?怎么解决?
内存泄露。用完记得将没用的ThreadLocal运行remove移除。
未完待续
更详细的资料参见 :
想进大厂需要懂的50个多线程面试题