多线程面试题和答案:线程锁+线程池+线程同步
- 1、并发编程三要素?
- 2、多线程的价值?
- 3、创建线程的有哪些方式?区别是什么?
- 4、创建线程的三种方式的对比?
- 4、线程的生命周期及五种基本状态及转换条件
- 5.什么是线程池? 有哪几种创建方式?
- 6、线程池的优点?
- 7.Java中的同步集合与并发集合有什么区别?
- 8、常用的并发工具类有哪些?
- 9.CyclicBarrier和CountDownLatch的应用场景?
- 10.CyclicBarrier和CountDownLatch的区别
- 11.synchronized的作用?
- 12.volatile关键字的作用?
- 13.什么是CAS?
- 14. CAS的问题?
- 15.什么是Future?
- 16.什么是AQS?
- 17.ReadWriteLock是什么?
- 18.FutureTask是什么?
- 19.synchronized和ReentrantLock的区别?
- 20.什么是乐观锁和悲观锁?
- 21.线程B怎么知道线程A修改了变量?
- 22.synchronized、volatile、CAS比较?
- 23.sleep方法和wait方法有什么区别?
- 24.ThreadLocal是什么?有什么用?
- 25.为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用?
- 26.多线程同步有哪几种方法?
- 27.线程的调度策略?
- 28.ConcurrentHashMap的并发度是什么?
- 29.Java死锁以及如何避免?
- 30.怎么唤醒一个阻塞的线程?
- 31.不可变对象对多线程有什么帮助?
- 32.什么是多线程的上下文切换?
- 33.如果你提交任务时,线程池队列已满,这时会发生什么?
- 34.Java中用到的线程调度算法是什么?
- 35.什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
- 36.Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?
- 37.单例模式的线程安全性
- 38.Semaphore有什么作用?
- 39.Executors类是什么?
- 40.线程类的构造方法、静态块是被哪个线程调用的?
- 41.同步方法和同步块,哪个是更好的选择?
- 42.Java线程数过多会造成什么异常?
1、并发编程三要素?
1)原子性
原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。
2)可见性
可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。
实现可见性的方法:
synchronized或者Lock:保证同一个时刻只有一个线程获取锁执行代码,锁释放之前把最新的值刷新到主内存,实现可见性。
3)有序性
有序性,即程序的执行顺序按照代码的先后顺序来执行。
2、多线程的价值?
1)发挥多核CPU的优势
多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的,采用多线程的方式去同时完成几件事情而不互相干扰。
2)防止阻塞
从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
3)便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
3、创建线程的有哪些方式?区别是什么?
线程创建的方法有三个:
1)继承Thread类创建线程类
2)通过Runnable接口创建线程类
3)通过Callable和Future创建线程
4、创建线程的三种方式的对比?
-
采用实现Runnable、Callable接口的方式创建多线程。
优势:线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。 -
使用继承Thread类的方式创建多线程
优势:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势:线程类已经继承了Thread类,所以不能再继承其他父类。 -
Runnable和Callable的区别
- Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。 Call方法可以抛出异常,run方法不可以。
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
4、线程的生命周期及五种基本状态及转换条件
1、Java线程具有五中基本状态
1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就
绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才
有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
- 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
- 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
- 其他阻塞 :通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
2、sleep(),wait(),yield()和join()方法的区别
- sleep()
sleep()方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。 - wait()
wait()方法需要和notify()及notifyAll()两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用,也就是说,调用wait(),notify()和notifyAll()的任务在调用这些方法前必须拥有对象的锁。注意,它们都是Object类的方法,而不是Thread类的方法。
wait()方法与sleep()方法的不同之处在于,wait()方法会释放对象的“锁标志”。当调用某一对象的wait()方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了notify()方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的notifyAll()方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。
除了使用notify()和notifyAll()方法,还可以使用带毫秒参数的wait(long timeout)方法,效果是在延迟timeout毫秒后,被暂停的线程将被恢复到锁标志等待池。
此外,wait(),notify()及notifyAll()只能在synchronized语句中使用,但是如果使用的是ReenTrantLock