Java面试知识点之线程篇(二)
前言:接上篇,这里继续对java线程相关知识点进行总结。
1.notify和notifyall的区别
notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。
nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,也不能决定哪个线程能够获取monitor。
参考:
https://www.iflym.com/index.php/code/201208190001.html
http://www.cnblogs.com/dolphin0520/p/3920385.html
http://www.cnblogs.com/xrq730/p/4853932.html
2.关于ThreadLocal
ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
在http://www.cnblogs.com/dolphin0520/p/3920407.html中,作者说“在进行get之前,必须先set,否则会报空指针异常”,其实是因为返回值为null,在进行类型转换的时候报出的异常,并不是在进行get前,必须先set,不进行get操作,也可以运行程序,但是必须将long改为Long包装类。
参考:
http://www.cnblogs.com/dolphin0520/p/3920407.html
https://www.cnblogs.com/zhangjk1993/archive/2017/03/29/6641745.html
http://www.cnblogs.com/xrq730/p/4854813.html
http://www.cnblogs.com/swiftma/p/6764821.html
3.线程的几种状态
Thread源码中定义了线程具有以下六种状态:新建状态(NEW)、运行状态(RUNNABLE)、 阻塞状态(BLOCKED)、等待状态(WAITING)、定时等待(TIMED_WAITING)及死亡状态(TERMINATED )。
在我们平时的理解中涉及五种状态:新建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Dead)。
参考:
http://www.cnblogs.com/skywang12345/p/3479024.html
https://blog.csdn.net/pange1991/article/details/53860651
https://www.cnblogs.com/GooPolaris/p/8079490.html
4.关于线程池及其创建过程
线程池:帮我们重复管理线程,避免创建大量的线程增加开销。具体相关知识,请参见下面的参考链接。
参考:
https://blog.csdn.net/u011240877/article/details/73440993
https://www.cnblogs.com/waytobestcoder/p/5323130.html
http://www.cnblogs.com/xrq730/p/4856453.html
http://www.crazyant.net/2124.html
http://www.cnblogs.com/dolphin0520/p/3932921.html
5.关于Lock接口
关于Lock可以和synchronized进行一个对比:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
参考:
http://www.cnblogs.com/dolphin0520/p/3923167.html
6.死锁
当多个线程相互等待已经被对方占用的资源时,就会产生死锁。
如何避免死锁:
1)不要在一条线程中嵌套使用多个锁。
2)不要在一条线程中嵌套占用多个计算机资源。
3)给锁和资源加超时时间:如果非要在一条线程中嵌套使用多个锁或占用多个资源,需要给锁、资源加超时时间,从而避免无限期的等待。
参考:
https://blog.csdn.net/u010425776/article/details/54233279
7.重排序
重排序指的是编译器、处理器在不改变程序执行结果的前提下,重新排列指令的执行顺序,以达到最佳的运行效率。
参考:
https://www.cnblogs.com/xll1025/p/6486170.html
8.线程间通信
下面的参考链接给出了几种线程间的通信方式,注意理解。
参考:
https://blog.csdn.net/u010425776/article/details/54341405
http://www.importnew.com/26850.html。注:文中的前两种方式会造成死锁,解决方式加sleep。
https://blog.csdn.net/u011514810/article/details/77131296
9.关于单例模式
单例模式在面试中,出现的概率极高,因为它的实现代码相对较少,因此需要特别注意,推荐使用枚举机制来实现单例模式。
参考:
https://www.jianshu.com/p/fc7fc57d4360
https://blog.csdn.net/gavin_dyson/article/details/70832185
10.如何保证多线程下 i++ 结果的正确性
如果使用volatile进行变量的修饰,是不能得到预期的结果的,因为volatile只能保证数据的可见性(获取到的是最新的数据,不能保证原子性,即volatile跟原子性没关系),要保证原子性对数据的累加,可以用AtomicInteger类,也可以用synchronized来保证数据的一致性。
参考:
11.阻塞式方法
阻塞式方法是指程序会一直等待该方法完成,期间不做其他事情,ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,异步和非阻塞式方法在任务完成前就返回。
12.Thread类中start()方法和run()方法的区别
只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码,也就说是顺序执行,具有确定性。
参考:
http://www.cnblogs.com/xrq730/p/5060921.html
by Shawn Chen,2018.3.27日,下午。