关于JAVA并发编程的那些事。
记录一下自己在学习并发编程的时候遇到的一些问题。方便自己查阅。
1.实现Runnable接口好在哪里?
从代码架构角度:具体的任务(run方法)应该和“创建和运行线程的机制”(Thread类)相解耦。
使用继承Thread类的方式的话,那么每次想新建一个任务,只能新建一个独立的线程们这样做的话损耗会比较大(比如重新创建一个线程,执行完毕
以后再销毁等。如果实际工作内容,也就是run()函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable和线程池,就可以大大减少这样的损耗。
继承Thread类以后,由于Java语音不支持多继承,这样就无法再继承其他的类,限制了可扩展性。
2.“实现Runnable接口并传入Thread类”和继承Thread类,然后重写run()方法的本质对比?
在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法最主要的区别是在于run()的了内容来源。
//以下代码在Thread类中,可以找到
@override
public void run(){
if(target!=null){
target.run();
}
}
实现Runnable接口最终调用target.run()。继承Thread类,是整个run()方法都被重写。
3.有几种创建线程的方法?
从不同的角度看,会有不同的答案。
典型答案是两种,分别是实现Runnable接口和继承Thread类
但是我们看原理,其实Thread类实现了Runnable接口,并且Thread类的run()方法,会发现其实哪两种本质是一样的。两种方式在实现多线程的本质上,并没有区别。
还有其他实现线程的方法,例如线程池等,他们也能新建线程,但是细看源码,并没有逃过本质,也是实现Runnable接口和继承Thread类。
结论:我们只能那个通过新建Thread类这一种方式来出创建线程,但是类里面的run方法有两种方式实现,的一种是重写run()方法,第二种是实现Runnable接口的run方法,然后再把该Runnable实例传给Thread类。除此之外,从表面上看线程池、定时器等工具类也能创建线程,但是他们的本质也是逃不过刚说的范围。
4.start方法的执行流程是什么?
检查线程状态,只有New状态的线程才能继续,否则会抛出IllegalThreadStateException。运行或已经结束的线程都不能再启动。
(这里可引申面试题:一个线程调用两次start()方法会出现什么情况?why?解题思路就是1异常2线程状态)
被加入线程组
调用start0()方法启动线程。
Tips:start方法是被synchronized修饰的方法,可以保证线程安全。并且由JVM产检的main方法和system组线程,并不会通过start来启动。
5.Java中如何正确停止线程?
用interrupt来请求停止线程。(仅仅是通知到被终止的线程应该停止运行了,被停止的线程自身拥有决定权。这是一个协作机制。)
想要停止线程,需要请求方,被停止方,子方法被调用方相互配合才行:
– a.作为被停止方:每次循环或者适时检测中断信号,并且在可能抛出interruptedException的地方处理该中断信号;
– b.请求方:发出中断信号。
– c.子方法调用方:要注意优先在方法抛出InterruptedException,或者在检查到中断信号时,再次设置中断状态。(Catch里会重置这个状态,需要再次设置中断状态,否则就被吞了)
最后,错误的方法: stop、suspend方法已经被废弃(stop容易造成脏数据)
volatile的boolean标记,无法处理长时间阻塞的情况(例如,生产者消费者模式中,就存在这样的情况,生产者生产速度快,消费者消费速度慢,生产者队列阻塞)
6.无法响应中断时如何停止线程?
如果线程阻塞是由于调用了wait()、sleep()或者join()方法,你可以中断线程,通过抛出interruptedException异常来唤醒该线程,但是对于不能响应InterruptedException的阻塞,并没有一个通用的解决方案。但是我们可以利用特定的其他可以响应中断的方法,比如Reentrantlock.lockInterruptibly(),比如关闭套接字使线程立即返回等方法来达到目的。答案有很多种,因为有很多原因会造成线程阻塞,所以针对不同的情况,唤起的方法不同。总结来说,如果不支持响应中断,就要用特定的方法来唤起。根据不同的类,调用不同的方法。
.可以响应中断而抛出InterruptedException的常见方法?
Object.wait()/wait(long)/wait(long,int)
Thread.sleep()/sleep(long,int)
Thread.join()/join(long)/join(long,int)
java.util.concurrent.BlockingQueue.take()/put(E)
java.util.concurrent.locks.Lock.lockInterruptibly()
java.util.concurrent.countDownLatch.await()
java.util.concurrent.cyclicBarrier.await()
java.uti.concurrent.Exchanger.exchange(V)
java.nio.channels.InterruptibleChannel相关方法
java.nio.channels.Selector相关方法
7.判断线程是否被中断的方法有哪些?
static boolean interrupted() //返回之后,清除标记
boolean isInterrupted() //不清除
//Tip:注意Thread.intterupted()的目的对象时“当前线程”,而不管本方法来自于哪个对象
8.线程都有哪几个状态?
6种状态
New:已经创建,但是还没有执行start方法
Runnable:一旦调用了start方法后,就一定会到Runnable,java中的Runnable对应操作系统里的ready和running状态
Blocked:当一个线程进入同步代码块(被Synchronized修饰),该锁被其他线程拿走了。线程变成Blocked。只有Synchronized才能rag线程进入这个状态。
Wating:等待
Time_waiting:计时等待
Terminated:死亡
9.线程相关方法
Thread类:
sleep相关、
join() :等待其他线程执行完毕
yield相关 :放弃已经获得的CPU资源
currentThread:获取当前线程的引用
start,run方法:启动线程相关
interrupt相关::中断线程
stop() suspend() resuem()相关 :已经废弃
Object类:
wait():让线程短暂休息
notify/notifyAll 相关 :唤醒线程
10.wait/notify/notifyAll的作用和用法?
阶段 方法和作用
阻塞阶段 调用wait()方法
唤醒阶段 1、另一个线程调用这个对象的notify方法且刚好被唤醒的是本线程。2、另外一个线程调用这个对象的notifyAll方法。3、过了wait(long timeOut)的规定的超时时间,如果传入0就是永久等待。4、线程自身调用了intterrupt()
遇到中断 wait阶段遇到中断会抛出异常,并且释放掉锁
11.wait、notify、notifyAll特点?性质?
用必须先拥有monitor锁。(Synchronized)
notify只能唤醒一个线程。
属于Object类,是所有对象的父类,所以任何对象都能调用,并且都是native final的。
类似Condition的功能
同时持有多个锁的情况。释放锁,只能释放现在wait所对应的对象的那把锁。
12.用wait/notify方法实现消费者生产者模式?
package com.yue.consumer;
import java.util.Date;
import java.util.LinkedList;
//使用wait notify实现一个生产者消费者模式
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage storage = new EventStorage();
Producer producer = new Producer(storage);
Consumer consumer =new Consumer(storage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable{
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
public void run() {
for (int i= 0;i<100;i++){
storage.put();
}
}
}
class Consumer implements Runnable{
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
public void run() {
for (int i=0;i<100;i++){
storage.take();
}
}
}
class EventStorage{
private int maxSize;
private LinkedList storage;
public EventStorage() {
this.maxSize = 10;
this.storage = new LinkedList();
}
public synchronized void put(){
while(storage.size() == maxSize){
try {
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("仓库里有了"+storage.size()+"个产品");
notify();
}
public synchronized void take(){
while(storage.size()==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}郑州妇科在线医生 http://www.zzkdfk120.com/
System.out.println("拿到了"+storage.poll()+",现在仓库还剩下"+storage.size());
notify();
}
}
13.为什么wait需要在同步代码块中使用,而sleep不需要?
为了让线程之间的通信更加可靠,防止死锁或者永久等待。如果不放在synchronized中的话,那么久有可能在线程执行到一个wait之前,切换到另外一个线程。而另外一个线程执行完notify后,切换回来。这样就没有线程去唤醒它了。而sleep是针对自己线程的,和其他线程的关系不大。
14.为什么wait、notify和notifyAll定义在object类中,sleep定义在Thread类中?
因为在Java中,这三个操作都是所级别的操作,而锁是针对对象的。锁是绑定到对象中,而不是绑定到线程。
15.wait是属于Object对象的,那调用Thread.wait()会出现什么情况?
会导致流程问题。因为在线程退出的时候,会自动执行一个notify
16.sleep方法的作用?
作用:让线程在预期执行,其他时候不占用CPU资源
特点:Sleep方法可以让线程进入waiting状态,并且不占用CPU资源,但是不释放锁(包含synchronized和lock),直到规定时间之后在执行。休眠期内如果被中断,会抛出异常并清除中断状态。
Tips:
//这两种方式其实都是一样的,但是第一种比较优雅
TimeUnit.SECONDS.sleep()
Thread.sleep()
17.wait和sleep方法的异同?
相同:
Wait和sleep方法都可以使线程阻塞,对应线程状态是Waiting或Time_Waiting。
wait和sleep方法都可以响应中断Thread.interrupt()。
不同点:
wait方法的执行必须在同步方法中进行,而sleep则不需要。
在同步方法里执行sleep方法时,不会释放monitor锁,但是wait方法会释放monitor锁。
sleep方法短暂休眠之后会主动退出阻塞,而没有指定时间的 wait方法则需要被其他线程中断后才能退出阻塞。
wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法
TIps:Java设计的时候把对象都当成一把锁,对象头中都有锁的状态
18. join()方法的作用?
作用:因为新线程加入我们,所以得等他执行完再出发;通常是,主线程等待子线程,而不是子线程等待主线程。例如一般是main等thread1执行完。join遇到中断时候是主线程被中断,是主线程抛出异常;在join期间状态是waiting
Tips:CountDownLatch或CyclicBarrier类封装了join。建议使用封装好的工具
源码:
调用了thread.wait方法,而这方法会在thread执行结束后悔自动调用notify。这也是为什么不要使用这个的原因。
19.yield()方法的作用?
作用:释放cpu时间片,线程状态是runnable,而不是bolcked,也不是waiting。常用于并发包中。
yield 和 sleep:
sleep期间属于被阻塞,yield不是阻塞,随时是runable状态。而且JVM是不保证遵循的。
20.线程都有哪些属性?
编号(ID):每个编程都有自己的ID,用于标识不同的线程。
名称(Name):作用是让用户或者程序员在开发、调试或运行过程中,更容易区分每个不同的线程,定位问题等。
是否是守护线程(isDeamon) :true代表该线程是守护线程,false代表线程是非守护线程,也就是用户线程。
– 作用:给用户线程提供服务。例如垃圾处理器。
– 特性:线程类型默认继承自父线程。被谁启动,一般都是JVM启动的,(main)。不影响JVM退出。
– 区别:整体无区别。唯一区别在于是否影响JVM退出。
优先级(Priority):优先级这个属性的目的是告诉线程调度器,用户希望哪些线程相对多运行,哪些少运行。
– 10个优先级,默认5.
– 程序的设计不应该优先级
21.实际工作中,如何全局处理异常?为什么要全局处理?不处理行不行?
主线程可以启动发现异常,子线程却不行。比如主线程操作非常多,子线程虽然报异常,但是日志太多,不好发觉。并且在子线程发现问题后,并没有停止执行。
子线程异常无法用传统方法捕获。
不能直接捕获的后果,可能线程挂掉打印堆栈。用了全局处理之后提高健壮性,可以在发生未知异常后,重启线程或者通知程序员等。
22.关于线程异常的两种处理方法?
方案一:手动在每个run()方法里进行try catch (不推荐)
方案二:利用UncaughtExceptionHanler接口
– void uncaughtException (Thread t,Throwable e)
– 异常处理器的调用策略:首先会检查父线程,一直往上找,查找是否有人能够处理。
– 实现:
首先,自定义一个类实现Thread.UncaughtExceptionHandler。重写内置方法uncaughtException(Thread t,Throwable e)方法,里面写自己的逻辑。(Tips:可以通过构造方法来传模块名字)
然后,在需要配置的类中setDefaultUncaughtExceptionHandler(new HandlerInstance);
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。