zoukankan      html  css  js  c++  java
  • 线程问题汇总

    一 线程状态转换图

    注意:调用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);


  • 相关阅读:
    读书笔记----软件设计原则、设计模式
    程伟杰 | 2021软件代码开发技术作业一 | 自我介绍+课程6问
    团队作业3-需求改进&系统设计
    团队项目作业2-需求规格说明书
    【Android实习】20场面试斩获大厂offer,我学会了什么
    通俗易懂,android是如何管理内存的
    关于Handler同步屏障你可能不知道的问题
    清晰图解深度分析HTTPS原理
    这一篇TCP总结请收下
    深入浅出Java线程池:源码篇
  • 原文地址:https://www.cnblogs.com/balfish/p/8650049.html
Copyright © 2011-2022 走看看