面试官必问java 并发知识总结-线程

Posted 神技圈子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试官必问java 并发知识总结-线程相关的知识,希望对你有一定的参考价值。

线程是什么

       线程是针对cpu来说的一个执行独立单元,线程的资源来自进程。进程内的线程是共享资源的(如内存  文件句柄  网络等)所以就有线程的并发冲突问题,进程间的资源是隔离的。java.lang.Thread来定义一个线程,并提供对线程的操作。

       进程的执行需要依赖线程(前面说了线程是CPU执行的一个最小独立单元),用户态的线程都是人为显示构建的。

     分为:用户态和内核态

      —>内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
       —>用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取

    为什么需要用户态和内核态?
         —>由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 :用户态 和 内核态

怎么创建一个线程并启动线程

      当前java里面有如下三种方式创建线程 

  1.     继承Thread类创建线程类
  2.     实现Runnable接口
  3.     通过Callable和Future创建线程

 

先说第一种继承Thread类创建 : 

   thread的构建函数有9种,支持不同的实现方式,简单说下如果参数里面的有string的,是让你定义线程名,这个对问题定位是有好处的,能够知道是哪个线程。如果带runnable的,则线程在启动时候会调用 Runnable 的run方法(必须实现run方法),如果有ThreadGroup的,则创建的线程会由该ThreadGroup管理。注意  你可以直接调用 Thread的run()方法,但是这个时候你不是以多线程方式运行程序,你直接调用了一个普通的方法 , 如果以多线程方式启动 只能调用 Thread.start()

第二种通过实现 Runnable接口来实现

       类似于  public  class   NewThread  implement  Runnable

                         @Override

                          public void run()

                                  //方法实现 System.out.println("aaaa");

                         

       

     若要启动线程,需要new Thread(Runnable target).start(),看了Thread 类的run方法就会看到

@Override
public void run() 
    if (target != null) 
        target.run();
    
    传进去的Runnale target对象的run()方法。但是这种方式有个好处。你可以继承其他类在实现 Runnable 接口

第三种方式 通过Callable和Future创建线程

   为什么要有这种方式?通过看代码你会发现 不管是 继承 Thread 或者实现 Runnable, run()方法是没有返回值的,

public abstract void run();

   而 Callable 和Future创建线程就能有返回值

@FunctionalInterface
public interface Callable<V> 
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;

还一个重要点的,Callable的call()方法是 可以throws  Exception的, run() 方法是不可以的。

    这种方式创建一个线程并启动的方式如下:

    1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值

    2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。 FutureTask 实现了public class FutureTask<V> implements RunnableFuture<V> ,
         而 RunableFutrue 又继承了public interface RunnableFuture<V> extends Runnable, Future<V>

    3. 使用FutureTask对象作为Thread对象的target,并调用thread的start()来启动

    4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

   补充几点: 1)Thread 类还有个setDaemon()方法,这个方法的作用是把线程设置为守护线程,在其他非守护线程退出后做一些善后工作。

                       2)当一个线程正常地运行结束或者抛出某种未检测的异常,线程就会终止。当线程终止之后,是不能被重新启动的。

                       3)同一个Thread上无法多次调用start()方法,如果调用了会抛出InvalidThreadStateException异常。

                      4) 如果线程已经启动但是还没有终止,那么调用isAlive方法就会返回true.如果线程已经被取消,那么调用其isAlive在什么时候返回false不太确定,可能有不同的实现。

线程优先级

        Java提供一个线程调度器来监视和控制Runnable状态的线程。线程的调度策略采用抢占式,优先级高的线程比优先级低的线程优先执行。在优先级相同的情况下,按照“先到先服务”的原则。

  依赖于jvm的实现,优先级不能保证线程一定会按序执行,会影响线程的调度。当前优先级会按照  Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之间(分别为1和10)

  这些优先级范围跟对应的说明如下 

优先级范围说明权重
10应急处理最大
7-9交互相关,事件驱动次大
4-6IO限制类
2-3后台计算
1仅在没有任何线程运行时运行的最小

 

 

 

 

 

 

 

   如果不做特殊设置,假如 A线程里面创建了B线程,则A与 B的线程优先级相同 。默认情况下线程的优先级是 5  ,可以通过下面例子证明 : 

    public static void main(String[] args) 

        Thread.currentThread().setPriority(4);
        Thread  th  = new Thread();
        System.out.println(th.getPriority());
    

输出  如下 

4
    public static void main(String[] args) 
        Thread  th  = new Thread();
        System.out.println(th.getPriority());
    

   输出如下 : 

5

但是你可以改这个线程的优先级  通过方法  :  th.setPriority(5);

但是线程的优先级在不同虚拟机的调度机制是不一样的,有些虚拟机就是把最高优先级的先执行,这样可能会导致优先级低的线程被饿死,有些是公平对待的,保证线程都能被调度到。

线程的控制

      线程其实是有方法把它挂起或者让他中断的,Thread 类也提供了对应的方法,下面列几种常见下线程的控制以及对应的状态情况: 线程无非就几种状态: 等待执行    正在执行    挂起   中断死亡  ,下面从这集中状态的控制来说明下线程的控制: 

1. sleep() & interrupt()
      对一个线程在其上调用sleep()暂停着,那么这个线程就挂起了,例如:  thread1.sleep(100) 单位是ms; 如果要取消他的等待状态,可以在正在执行的线程里(非thread1),thread1.interrupt();
   这个操作产生的效果就是thread1放弃睡眠操作,当在sleep中时 线程被调用interrupt()时,就马上会放弃暂停的状态.并抛出 InterruptedException .丢出异常的,是 thread1线程.
2. wait() & interrupt()
     对线程thread1调用了wait()进入了等待状态,也可以通过调用thread1.interrupt().不过这时候要小心锁定的问题.线程在进入等待区, 会把锁定解除 ,当对等待中的线程调用 interrupt()时,会先重新获取锁定,再抛出异常.在获取锁定之前,是无法抛出异常的.
3. join() & interrupt()
    当线程以join()等待其他线程结束时,当它被调用interrupt(),它与sleep()时一样, 会马上跳到 catch 块里. 注意interrupt()方法,一定是调用被阻塞线程的interrupt方法.如在线程a中调用来线程t.join().
    则a会等t执行完后在执行t.join后的代码,当在线程b中调用来 a.interrupt()方法,则会抛出InterruptedException,当然join()也就被取消了。


       每个线程都有一个相关的Boolean类型的中断标识。在线程t上调用t.interrupt会将该线程的中断标识设为true,除非线程t正处于Object.wait,Thread.sleep,或者Thread.join,这些情况下interrupt调用会导致t上的这些操作抛出InterruptedException异常,但是t的中断标识会被设为false。任何一个线程的中断状态都可以通过调用isInterrupted方法来得到。如果线程已经通过interrupt方法被中断,这个方法将会返回true。

       起初,Thread类还支持一些另外一些控制方法:suspend,resume,stop以及destroy。这几个方法已经被声明过期。其中destroy方法从来没有被实现,估计以后也不会。而通过使用等待/唤醒机制增加suspend和resume方法在安全性和可靠性上还不成熟。

 

静态方法

Thread类中的部分方法被设计为只能当前正在运行的线程(即调用Thread方法的线程,这些方法被搞了 public  static的。

  • Thread.currentThread方法会返回当前线程的引用,得到这个引用可以用来调用其他的非静态方法,比如Thread.currentThread().destroy(),销毁线程,只是该方法当前不建议用了。
  • Thread.interrupted方法会清除当前线程的中断状态并返回前一个状态。(一个线程的中断状态是不允许被其他线程清除的)
  • Thread.sleep(long msecs)方法会使得当前线程暂停执行至少msecs毫秒。

  常用的方法就这些,其他的可以暂时不用关注。

线程组

     每一个线程都是一个线程组中的成员。默认情况下,线程组是继承性的,比如新建线程和创建它的线程属于同一个线程组。但是当创建一个新的线程组,这个线程组就会变成当前线程组的子线程组,类似子节点的概念。你如果想看当前线程属于哪个线程组,调用线程的getThreadGroup方法会返回所属的线程组,ThreadGroup类也有方法可以得到哪些线程目前属于这个线程组,比如enumerate方法。

  为什么要有ThreadGroup的概念?:

        1)安全隔离:比如对不属于同一组的线程调用interrupt是不合法的。这是为避免某些问题(比如,一个applet线程尝试杀掉主屏幕的刷新线程)所采取的措施。T

         2)可以通过ThreadGroup也可以为该组所有线程设置一个最大的线程优先级。

实际的使用中线程组往往不会直接在程序中被使用。如果想通过程序看下线程如何分组的,可以通过一个vector等类来实现,那么普通的集合类(比如java.util.Vector)应是更好的选择。但是线程组的uncaughtException方法却是非常有用的,如果线程因为运行时异常而中断的时候,调用这个方法可以打印出线程的调用栈信息。

以上是关于面试官必问java 并发知识总结-线程的主要内容,如果未能解决你的问题,请参考以下文章

面试官必问java 并发知识总结-同步与锁

面试官必问的信号量与生产者消费者问题

阿里Java面试答案PDF文档免费领

大厂面试官必问的Mysql锁机制

面试官必问的 3 道 MQ 面试题,还有谁不会??

Java面试知识点1——多线程和并发编程