线程问题汇总

一 线程状态转换图

注意:调用obj.wait()的线程须要先获取obj的monitor,wait()会释放obj的monitor并进入等待态。因此wait()/notify()都要与synchronized联用java

 

二 阻塞和等待的区别

阻塞: 当一个线程试图得到对象锁(非juc库中的锁,即synchronized),而该锁被其余线程持有,则该线程进入阻塞状态。它的特色是使用简单,由jvm调度器来决定唤醒本身,而不是须要由另外一个线程来显式唤醒本身,不响应中断api

等待: 当一个线程等待另外一个线程通知调度器一个条件时,该线程进入等待状态。它的特色是须要等待另外一个线程显式地唤醒本身,实现灵活,语意更丰富,可响应中断。例如: Object.wait(), Thread.join() 以及等待lock或condition安全

须要强调的是虽然synchronized和JUC里的Lock都实现锁的功能,但线程进入的状态是不同的。synchronized会让线程进入阻塞态,而JUC里的lock是用LockSupport.park()/unpark()来实现阻塞/唤醒的,会让线程进入等待态。但事实上,虽然等待锁时进入的状态不同,但被唤醒后又都进入runnable态,从行为效果来看又是同样的。多线程

 

三 几个方法的使用和坑

1 sleep

sleep至关于让线程睡眠,交出cpu,让cpu去执行其余的任务。可是sleep不会释放锁,也就是说若是当前线程持有对某个对象的锁,则即便调用sleep方法,其余线程也没法访问这个对象。框架

2 yield

调用yield方法会让当前线程交出cpu权限,让cpu去执行其余的线程。它跟sleep方法类型,一样不会释放锁。可是jield不能控制具体的交出cpu的时间。jvm

注意调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只须要等待从新得到Cpu执行时间。ide

3 join

join有三个重载版本函数

1 join()
2 join(long millis)     //参数为毫秒
3 join(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

join()实际是利用了wait(),只不过它不用等待notify()/notifyAll(),且不受其影响。它结束的条件是:1)等待时间到;2)目标线程已经run完(经过isAlive()来判断)。this

join和synchronized的区别是: join在内部使用wait()方法进行等待,而synchronized关键字使用的是"对象监视器"做为同步spa

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    
    //0则须要一直等到目标线程run完
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        //若是目标线程未run完且阻塞时间未到,那么调用线程会一直等待。
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}
View Code

4 interrupt

此操做会中断等待中的线程,并将线程的中断标志位置位。若是线程在运行态则不会受此影响

能够经过如下三种方式来判断中断:

1)isInterrupted()

此方法只会读取线程的中断标志位,并不会重置。

2)interrupted()

此方法读取线程的中断标志位,并会重置。

3)throw InterruptException

抛出该异常的同时,会重置中断标志位。

5 suspend/resume

挂起线程,直到被resume,才会苏醒

但调用suspend()的线程和调用resume()的线程,可能会由于争锁的问题而发生死锁,因此JDK 7开始已经不推荐使用了。

 

 

四 相关知识汇总

1 什么是线程,和进程的区别

线程是操做系统可以进行运算调度的最小单位,它被包含在进程之中,是进程中实际运做单位。

线程是进程的子集,一个进程能够有不少线程,每条线程并行执行不一样的任务。不一样的进程使用不一样的内存空间,而全部的线程共享一片相同的内存空间。别把它和栈内存搞混,每一个线程都拥有单独的栈内存用来存储本地数据。

 

2 java如何中止一个线程

Java提供了很丰富的API但没有为中止线程提供API。JDK 1.0原本有一些像stop(), suspend() 和 resume()的控制方法。可是因为潜在的死锁威胁,在后续的JDK版本中他们被弃用了,以后Java API的设计者就没有提供一个兼容且线程安全的方法来中止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,若是要手动结束一个线程,你能够用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

    private class Runner extends Thread{
    volatile boolean bExit = false;
  
    public void exit(boolean bExit){
        this.bExit = bExit;
    }
  
    @Override
    public void run(){
        while(!bExit){
            System.out.println("Thread is running");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ex) {
                    Logger.getLogger(ThreadTester.class.getName()).log(Level.SEVERE, null, ex);
                }
        }
    }
}

 

3 为何Thread类的sleep()和yield()方法是静态的

Thread类的sleep和yield都是做用在当前正在执行的线程上运行,因此其余处于等待状态的线程上调用这些方法是没有意义的。设置为静态代表在当前执行的线程上工做,避免开发错误地认为能够在其余非运行线程调用这些方法。

 

4 在java中wait和sleep方法的不一样

最大的不一样是: 在等待时wait会释放锁,而sleep一直持有锁。wait一般用于线程间交互,sleep一般被用于暂停执行。

 

5 线程的优先级

Java中线程的优先级分为1-10这10个等级,若是小于1或大于10则JDK抛出IllegalArgumentException()的异常,默认优先级是5。在Java中线程的优先级具备继承性,好比A线程启动B线程,则B线程的优先级与A是同样的。注意程序正确性不能依赖线程的优先级高低,由于操做系统能够彻底不理会Java线程对于优先级的决定。

 

6 为何wait和notify方法要在同步块中调用

java api强制要求这么作,不然会抛出IllegalMonitorStateException异常。还有一个缘由是为了不wait和notify之间产生竞态条件。

 

7 java线程池中submit()和execute()方法有什么区别

二者均可以向线程池提交任务,execute()方法的返回类型是void, 它定义在Executor接口中,而submit()方法能够返回有计算结果获得Future对象,它定义在ExecutorService接口中,它扩展了Executor接口。

 

8 java中Runnable和Callable有什么不一样

二者都表明那些要在不一样的线程中执行的任务。Runnable从jdk1.0就开始有了,Callable是在jdk1.5增长的。它们的主要区别是Callable的call()方法能够返回值和抛出异常,而Runnable的run()没有这些功能。Callable能够装载有计算结果的Future对象。

 

9 为何wait, notify, notifyAll这些方法不在thread类里面

主要是由于java提供的锁是对象级的而不是线程级的,每一个对象都有锁,经过线程得到。wait,notify和notifyAll都是锁级别的操做,因此把它们定义在Object类中由于锁属于对象。

 

10 守护进程

Java中有两种线程,一种是用户线程,另外一种是守护线程。当进程中不存在非守护线程了,则守护线程自动销毁。经过setDaemon(true)设置线程为后台线程。注意thread.setDaemon(true)必须在thread.start()以前设置,不然会报IllegalThreadStateException异常;在Daemon线程中产生的新线程也是Daemon的;在使用ExecutorService等多线程框架时,会把守护线程转换为用户线程,而且也会把优先级设置为Thread.NORM_PRIORITY。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑

 

11 wait, notify, notifyAll用法

首先要明确,只能在synchronized同步方法或者同步代码块中使用这些。在执行wait方法后,当前线程释放锁(这点与sleep, yield不一样)。调用了wait函数的线程会一直等待,直到有其余线程调用了同一个对象的notify或notifyAll方法。须要注意的是,被唤醒并不表明马上得到对象的锁,要等待执行notify方法的线程执行完,也即退出synchronized代码块后,当前线程才会释放锁,进而wait状态的线程才能够得到该对象锁

不在同步代码块会有IllegalMonitorStateException异常(RuntimeException)

* @throws  IllegalMonitorStateException  if the current thread is not
* the owner of the object's monitor.

notify方法只会(随机)唤醒一个正在等待的线程,而notifyAll方法会唤醒全部正在等待的线程。若是一个对象以前没有调用wait方法,那么调用notify方法是没有任何影响的

 

12 interrupted和isInterrupted的区别

interrupted   判断当时线程是否已是中断状态,执行后清除状态标志

isInterrupted 判断当时线程是否已是中断状态,执行后清除状态标志

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
    return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
相关文章
相关标签/搜索