Java多线程问题总结
本文大部分为整合内容,会参考不少其他的技术博客。如有问题,请联系我。
目录:
一:java线程状态和转化过程
二:现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
三. Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。
一:java线程状态和转化过程
参考:https://www.cnblogs.com/happy-coder/p/6587092.html
线程转化图:
说明:
线程共包括以下5种状态。
1. 新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
二:现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
主要考察,Thread的join()方法,看下api解释: 等待该线程终止。
代码如下:
public class ThreadTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Thread thread1 = new Thread(){ @Override public void run() { // TODO Auto-generated method stub try { System.out.println("thread 1 running...."); sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ System.out.println("thread 1 stoped...."); } super.run(); } }; Thread thread2 = new Thread(){ @Override public void run() {...} }; Thread thread3 = new Thread(){ @Override public void run() {...} }; try { thread1.start(); thread1.join(); thread2.start(); thread2.join(); thread3.start(); thread3.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
三. Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。
常见的锁:
Synchronized,它就是一个:非公平,悲观,独享,互斥,可重入的重量级锁,又叫内置加锁。
ReentrantLock,它是一个:默认非公平但可实现公平的,悲观,独享,互斥,可重入,重量级锁。
ReentrantReadWriteLocK,它是一个,默认非公平但可实现公平的,悲观,写独享,读共享,读写,可重入,重量级锁。
Synchronized的优缺点:
synchronized又称为内置锁,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁。
问题:1-那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
2-不是太灵活,实现读写锁用synchronized就实现不了。而lock加锁更灵活,对锁的粒度有更好的控制效果。
Lock就可以解决上面的两个问题,缺点是,lock必须手动释放锁,而synchronized是不需要手动释放。所以更加危险。
lock的使用方法:
Lock lock = ...; lock.lock(); try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 }
如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。
这个实质上就是读写锁的应用。可以直接使用ReadWriteLock接口实现;
代码如下:
public class ReadWriteMap<K,V> { private final Map<K,V> map; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); public ReadWriteMap(Map<K,V> map){ this.map = map; } //remove,putAll等修改内容的方法类似 public void put(K key,V value){ writeLock.lock(); try{ map.put(key,value); }finally { writeLock.unlock(); } } //只读操作 public V get(K key){ readLock.lock(); try{ return map.get(key); }finally { readLock.unlock(); } } }
事实上,Java自带的ConcurrentHashMap已经能很好的实现上述的功能。
四:Java 中 wait
和 sleep
方法有什么区别
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。
五:如何在 Java 中实现一个阻塞队列?
阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列。
代码:
public class MyBlockingQueue { private List queue = new LinkedList(); private int limit = 10; public MyBlockingQueue(int limit){ this.limit = limit; } public synchronized void enqueue(Object item) throws InterruptedException { while(this.queue.size() == this.limit) { wait(); } if(this.queue.size() == 0) { notifyAll(); } this.queue.add(item); } public synchronized Object dequeue() throws InterruptedException{ while(this.queue.size() == 0){ wait(); } if(this.queue.size() == this.limit){ notifyAll(); } return this.queue.remove(0); } }
如果用 Java 5 的并发类实现,基本上就是用java已经实现的类,例如BlockingQueue接口实现。
六 如何在 Java 中编写代码解决生产者消费者问题?
阻塞队列的使用。
七 写一段死锁代码。你在 Java 中如何解决死锁?
死锁发生需要必备的四个条件:
1-互斥条件,资源中必须有一个不能被共享;
2-至少有一个任务它必须持有一个资源且正在等待获取另外一个当前被别的任务持有的资源;
3-资源不能被任务抢占;
4-必须有循环等待。
死锁代码:
public static void main(String[] args) throws InterruptedException { final DeadLock dd1 = new DeadLock(); final DeadLock dd2 = new DeadLock(); Thread t1 = new Thread(new Runnable() { public void run() { //首先获得dd1的锁
synchronized (dd1){ //休眠 try { Thread.sleep(50); synchronized (dd2) { System.out.println(Thread.currentThread().getName() + "线程。。"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }, "t1"); Thread t2 = new Thread(new Runnable() { public void run() { synchronized (dd2) { try { synchronized (dd1) { System.out.println(Thread.currentThread().getName() + "线程。。"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }, "t2"); t1.start(); t2.start(); }
如何解决死锁(只需破坏到死锁的四个条件之一就可):
1-加锁顺序,所有的线程都按照顺序获取锁。破坏掉第二条。
2-加锁时限,在获取锁的时候加一个超时时间,如果超时,则释放已占用的锁。破坏掉第四条。
3-死锁检测。破坏掉第二条。
八:什么是原子操作?Java 中有哪些原子操作?
原子操作:不可中断的操作。
常见的原子类:AtomicInteger、AtomicLong
九:Java 中 volatile
关键字是什么?你如何使用它?它和 Java 中的同步方法有什么区别?
使用volatile修饰的变量会强制将修改的值立即写入主存,主存中值的更新会使缓存中的值失效。
volatile具有可见性、有序性,不具备原子性。
有序性:即程序执行时按照代码书写的先后顺序执行。
volatile适用场景
1-适用于对变量的写操作不依赖于当前值,对变量的读取操作不依赖于非volatile变量。
2-适用于读多写少的场景。
3-可用作状态标志。
4-JDK中volatie应用:JDK中ConcurrentHashMap的Entry的value和next被声明为volatile,AtomicLong中的value被声明为volatile。AtomicLong通过CAS原理(也可以理解为乐观锁)保证了原子性。
volatile VS synchronized
volatile synchronized修饰对象修饰变量修饰方法或代码段可见性11有序性11原子性01线程阻塞01对比这个表格,你会不会觉得synchronized完胜volatile,答案是否定的,volatile不会让线程阻塞,响应速度比synchronized高,这是它的优点。
十. 什么是竞态条件?你如何发现并解决竞态条件?
竞争状态:可能发生在临界状态内的特殊状态。临界状态是被多个线程执行的一段代码,在这个代码段中,线程的执行顺序影响临界状态的并发执行结果。
如何解决竞争状态,基本原则有三种方式:
1-减少锁的持有时间;
2-降低锁的请求频率;
3-使用带有协调机制的独占锁。例如读写锁
具体方式:
1-缩小锁的范围;
2-锁分解(减少锁的粒度),如果一个锁需要保护多个相互独立的状态变量,那么可以将这个锁分解多个锁。并且每个所只保护一个变量。从而提高可伸缩性,并最终降低每个锁被请求的频率;
3-锁分段,例如,ConcurrentHashMap的实现中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16。
4-避免热点域,将一些反复计算的结果缓存起来,类似于批处理。
5-使用带有协调机制的独占锁,例如,读写锁。
10. 在 Java 中你如何转储线程(thread dump)?如何分析它?
在 UNIX 中,你可以使用 kill -3
然后线程转储日志会打印在屏幕上,可以使用 CTRL+Break
查看。这只是一个较简单的线程面试题,狡猾一点的话他们会问你如何分析转储日志。线程转储日志对于分析死锁情况非常有用。
11. 既然 start()
方法会调用 run()
方法,为什么我们调用 start()
方法,而不直接调用 run()
方法?
这是一个基本的 Java 多线程面试题。最初,我刚开始多线程编程时对此还有些困惑。如今我一般在 Java 中级面试的电话面试或一轮面试中遇到。
这道问题的答案是这样的。当你调用 start()
方法时,它会新建一个线程然后执行 run()
方法中的代码。如果直接调用 run()
方法,并不会创建新线程,方法中的代码会在当前调用者的线程中执行。可以看这篇文章了解更多线程中 Start 和 Run 方法的区别。
12. Java 中你如何唤醒阻塞线程?
这是有关线程的一个很狡猾的问题。有很多原因会导致阻塞,如果是 IO 阻塞,我认为没有方式可以中断线程(如果有的话请告诉我)。另一方面,如果线程阻塞是由于调用了 wait()
,sleep()
或 join()
方法,你可以中断线程,通过抛出 InterruptedException
异常来唤醒该线程。可以看这篇文章了解有关处理阻塞线程的知识Java 中如何处理阻塞方法。
13. Java 中 CyclicBarriar
和 CountdownLatch
有什么区别?
最近的 Java 线程面试题多数在测试你对 JDK 5 并发包的掌握程度。两者区别之一就是 CyclicBarrier
在屏障打开之后(所有线程到达屏障点),可以重复使用。而 CountDownLatch
不行。想了解更多可以参与课程Java 中的多线程和并行计算。
14. 什么是不可变类?它对于编写并发应用有何帮助?
尽管这道面试题和线程没有直接关系,但间接影响也很大。如果面试官随后让你写一个不可变类,或问你为什么 Java 中的 String 是不可变的,会让面试题变得更加复杂。
15. 你在多线程环境中遇到的最多的问题是什么?你如何解决的?
内存干扰、竞态条件、死锁、活锁、线程饥饿是多线程和并发编程中比较有代表性的问题。这类问题无休无止,而且难于定位和调试。
这是基于经验给出的 Java 面试题。你可以看看Java 并发实战课程来了解现实生活中高性能多线程应用所面临的问题。
上面所说的是我喜欢的,也是投行最常问的 Java 线程面试题。这个清单并不完整,所以可以在下方评论出你在面试中遇到的有意思的 Java 线程题目。这篇文章收集并分享与多线程概念有关的面试题,不仅仅有助于面试,还为大家打开多线程概念的大门。
有位读者提供了一些 Java 线程面试题。补充在下面:
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 深入浅析Python2.x和3.x版本的主要区别
- 用户态进程如何得到虚拟地址对应的物理地址?
- Linux中利用grep命令如何检索文件内容详解
- laravel框架学习笔记之组件化开发实现方法
- PHP7创建销毁session的实例方法
- Vim如何使用相对行号实现一切操作详解
- laravel 框架执行流程与原理简单分析
- 对python自动生成接口测试的示例讲解
- 解决pip install xxx报错SyntaxError: invalid syntax的问题
- PHP如何使用JWT做Api接口身份认证的实现
- 解决python3 Pycharm上连接数据库时报错的问题
- php把文件设置为插件的技巧方法
- Python中fnmatch模块的使用详情
- tp5框架使用cookie加密算法实现登录功能示例
- python实现停车管理系统