关于Java的并发编程

  关于JAVA并发编程的那些事。

  记录一下自己在学习并发编程的时候遇到的一些问题。方便自己查阅。

  1.实现Runnable接口好在哪里?

  从代码架构角度:具体的任务(run方法)应该和“创建和运行线程的机制”(Thread类)相解耦。

  使用继承Thread类的方式的话,那么每次想新建一个任务,只能新建一个独立的线程们这样做的话损耗会比较大(比如重新创建一个线程,执行完毕

  以后再销毁等。如果实际工作内容,也就是run()函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable和线程池,就可以大大减少这样的损耗。

  继承Thread类以后,由于Java语音不支持多继承,这样就无法再继承其他的类,限制了可扩展性。

  2.“实现Runnable接口并传入Thread类”和继承Thread类,然后重写run()方法的本质对比?

  在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法最主要的区别是在于run()的了内容来源。

  //以下代码在Thread类中,可以找到

  @override

扫描二维码关注公众号,回复: 8291502 查看本文章

  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);


猜你喜欢

转载自blog.51cto.com/14335413/2460998