线程问题汇总

一 线程状态转换图

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

二 阻塞和等待的区别

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

等待: 当一个线程等待另一个线程通知调度器一个条件时,该线程进入等待状态。它的特点是需要等待另一个线程显式地唤醒自己,实现灵活,语意更丰富,可响应中断。例如: 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的时间。

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

3 join

join有三个重载版本

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

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

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

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


原文地址:https://www.cnblogs.com/balfish/p/8650049.html