Java线程

  1. 并行和并发的区别?
    • 并行:多个处理器同时处理多个任务
    • 并发:一个处理器处理多个任务,按照细分时间交替执行,在逻辑上是同时执行的
  2. 线程同步和线程通信的理解?
    • 线程同步:指的是当一段代码,正在被一个线程执行时,不能存在其他线程也在执行。Java给我们提供了两种方式:
      • synchronize(同步监视器):
        • 特点:不能修饰构造器与成员变量,能修饰类、方法和代码块;自动锁
        • 以下情况会导致synchronize释放锁:
          • 当代码块或方法执行完毕时
          • 线程在代码块或方法中遇到return、break时
          • 线程在代码块或方法中遇到未处理的异常时
          • 线程在代码块或方法中遇到锁对象使用wait()方法时
      • 同步锁(Lock):通过显示定义同步锁对象实现同步
        • 特点:只能修饰代码块;需要自己手动的加锁和释放锁
        • ReentrantLock是Lock的实现类,具有可重入性。可重入性指的是,线程可以在被加锁的ReentrantLock的锁上再次加锁
        • ReentrantLock对象维持一个计数器追踪lock()方法的嵌套调用,所以一个被锁保护的代码可以调用另一个被锁保护的代码
    • 线程通信:线程之间的协调执行
        • 线程通信是基于synchronize机制和条件变量完成的。通过查询执行条件,判断是执行还是继续等待,继续等待就执行锁对象的wait()方法。继续执行,执行完毕后,改变条件变量,并且使用notify唤醒其他线程,如此循环
        • 例:一个银行账户,系统命令一直存钱和取钱
                     
  3. 线程和进程的区别?
    • 在一个程序中,至少拥有一个进程,一个进程至少拥有一个线程
  4. Java中的竞态条件是什么?如何解决?
    • 在一个程序中,多个线程访问同一资源,如果对资源访问顺序敏感(如:加减乘除),就称存在竞态条件
    • 竞态条件一般发生在代码的临界区,如:
    • 当两个线程同时执行add方法,分别是add(2)和add(3),正常情况得出的结果应该是5,但是如果两个线程在获取count的值时count都为0的话,结果也就不会是5
    • 解决方式:使用synchronize或者Lock显示锁
  5. 线程运行时发生异常会怎么样?
    • 如果异常已经被捕获并且抛出,那么线程会继续执行
    • 如果异常没有被捕获那么线程会停止执行,并抛出Tread.UncaughExceptionHandler异常,此接口是处理导致线程停止执行的未捕获异常的内嵌接口
  6. 说说synchronize的锁升级原理?
    • 在锁对象中设置thread Id值,在第一次使用时thread Id为空,JVM让其持有偏向锁,并且将thread Id设置为此线程的Id,当下一次进行访问时,会判断thread Id和此线程Id是否相同,相同则直接使用此对象;不同,则JVM就会将锁升级为轻量级锁,通过自旋循环一定次数获得锁,如果循环了一定次数没有获得锁,那么就会将锁升级为重量级锁来获得锁。此过程就是锁的升级
    • 锁的升级目的是为了降低所带来的性能消耗
  7. 在Java程序中怎么保证多线程的运行安全?
    • 使用java.util.concurrent中的类
    • 使用自动锁synchronize
    • 使用手动锁Lock
  8. Java中堆和栈有什么不同?
    • 职责:
      • 栈:用于存储变量和方法的调用
      • 堆:存储Java中的对象,无论是局部变量、成员变量还是类变量,他们指向的对象都存储在堆内存中
    • 共享:
      • 栈:属于单个线程,每个线程都有着自己的栈内存,线程中的数据对于其他线程都是 不可见的
      • 堆:堆内存中的对象对所有线程都是可见的
    • 异常错误:
      • 栈:如果没有空间存储变量和方法调用,会抛出Java.lang.StackOverFlowError
      • 堆:如果没有空间存储生成的对象,会抛出Java.lang,OutOfMemoryError
    • 空间大小:堆内存远远大于栈内存
  9. 线程池是什么?为什么使用它?
    • 线程池的四大基本组成部分:
      • 线程池管理器(ThreadPool):主要负责创建线程池、销毁线程池以及创建新任务
      • 工作线程池(PoolWorker):线程池的线程,没有执行任务时处于等待状态,能够循环执行任务
      • 任务接口(Task):任务必须实现的接口,目的是使工作线程调度任务的执行,它主要是任务的入口、任务结束后的收尾操作以及获得任务的执行状态等
      • 任务队列(TaskQueue):存放未处理的任务队列,提供一个缓冲机制
    • 在线程需要被多次使用时,反复的创建和销毁线程,对于资源消耗是很大的,而线程池能够减少线程的创建和销毁,让线程能够多次被使用,我们也可以根据需要来控制创建线程的数量,这样我们就能减少内存的使用以及资源消耗
  10. 什么是死锁?
    • 当两个或两个以上的线程,因为资源的竞争而导致线程等待的状态,如:当A线程拥有独占锁a,尝试获取独占锁b时,同时B线程拥有独占锁b,尝试获取独占锁a时,此时就发生了锁的竞争,AB两个线程都拥有彼此需要的锁,这就是死锁
  11. 如何避免死锁?
    • 尽量使用java.util.concurrent中的类代替自己手写锁
    • 尽量使用tryLock(long timeOut,TimeUnit unit)方法,设置超时时间,超时即可退出
    • 降低锁的粒度,避免多个功能使用同一个锁
    • 减少同步代码块的使用
  12. 说说Java中饥饿和活锁?
    • 饥饿:一个线程一直占用资源导致其他线程处于等待状态,类似死锁,但是不同于死锁的是在此线程结束后会释放资源
    • 活锁:状态在一直改变,但不会执行。如:程序中存在一个资源,多个线程都将执行机会给其他线程,导致资源来回在多个线程中,但又得不到执行
  13. 说说线程同步的方法?
    • wait():Object类中的方法,使一个线程处于等待状态,会将对象锁释放
    • sleep():Thread类中的方法,使一个线程在指定时间内处于休眠状态,休眠时间结束就会正常执行,不会释放对象锁
    • notify():Object类中的方法,随机唤醒一个线程,由JVM控制
    • notifyAll():Object类中的方法,唤醒所有线程
  14. 线程的基本概念、线程的基本状态以及状态之间的关系?
    • 在一个进程中,执行进程代码的一个操作单元,一个进程至少有一个线程,即进程本身
    • 线程的基本状态:
      • 新建状态:使用new创建一个线程对象,与其他新建对象一样都只分配了内存
      • 等待状态:在new创建对象之后使用start()方法之前
      • 就绪状态:线程对象调用start()方法,JVM将线程放入可运行池中,等待分配CPU使用权
      • 运行状态:此状态的线程持有CPU使用权,执行程序中的代码
      • 阻塞状态:线程因为某些原因导致线程阻塞,停止执行。阻塞状态的线程JVM不会分配CPU的使用权,直到重新处于就绪状态,线程才有机会被分配到CPU的使用权
        • 阻塞状态分为3种:
          等待阻塞:线程对象调用wait()方法,线程释放对象锁,JVM将线程放入等待池中
          同步阻塞:线程对象需要得到资源锁,此资源锁又被其他线程所占用时,JVM会将线程放入锁池中,等待资源锁被释放并竞争资源锁
          其他阻塞:线程对象调用sleep()方法,或发送IO请求时,JVM会将线程置为阻塞状态,在sleep()休眠时间结束,或IO请求处理完毕之后,线程会重新进入就绪状态
      • 死亡状态:run方法被执行完,或遇到未捕获的异常线程终止执行,此时线程就处于死亡状态,结束线程的生命周期
    • 状态之间的关系:
  15. 创建线程有哪几种方法?
    • 继承Thread类,执行run()方法
    • 实现Runnable接口
    • 实现Callable接口
  16. Java中Runnable和Callable有什么不同?
    • Runnable:run()方法无返回值
    • Callable:call()方法有返回值
  17. sleep()和wait()有什么区别?
    • sleep():来自Tread类,不会释放锁,休眠时间结束重新进入就绪状态
    • wait():来自Object类,会释放锁,需要使用notify/notifyAll进行唤醒,将等待池中的线程移到锁池中,然后进行锁的竞争
  18. Java中的volatile变量是什么?
    • volatile是一种稍弱的同步机制,volatile修饰的变量不会执行加锁操作,所以不会导致线程的阻塞,volatile是比synchronize更轻量级的同步机制
    • 特点:
      • volatile能够保证线程修改的可见性【即:当一个线程对共享内存中的变量进行修改时,其他线程能够立即获得修改之后的值】,volatile能够将新值立即同步到主内存中,每次使用时也会立即在主内存中刷新
      • 提供了禁止指令重排序的优化,线程在使用volatile修饰的变量时,JVM会先执行一个操作,这个操作就是内存屏障【即:先于指令的操作必须先执行,后于指令的操作必须后执行】
      • 没有被volatile修饰的变量,线程在使用时会先将内存中的变量存入CPU缓存中;volatile修饰的变量,JVM保证每次读写都在内存中执行
    • 性能:读性能和普通变量几乎相同,但是写性能稍慢,因为需要在本地代码中插入很多内存屏障指令来保证处理器不发生乱序
    • 缺点:
      • volatile不能保证原子性,原子性就是一个不可分割的操作
        例:volatile int a=0; a++; 
        • a++ 分为三个步骤执行:
          • 内存到寄存器
          • 寄存器自增
          • 写入内存
        • 对于变量a而言具有修改可见性,但a++三个步骤都能够被分离开,不符合原子性的操作,所以存在线程安全问题
  19. synchronize和volatile的区别?
    • volatile是变量的修饰符,synchronize是修饰类、方法和代码块的
    • volatile只能保证修改的可见性,不能保证原子性,synchronize既能保证修改的可见性,又能保证原子性
    • volatile不会造成线程阻塞,synchronize可能会造成线程阻塞
  20. Java中synchronize和ReentrantLock的区别? 
    • ReentrantLock使用比较灵活,但是需要手动释放锁
    • ReentrantLock必须手动加锁和释放锁,synchronize不需要手动加锁和释放锁
    • ReentrantLock只适用于代码块,synchronize可用于类、方法、代码块中
  21. synchronize和Lock的区别?
    • synchronize是自动锁,Lock需要自己手动加锁和释放锁
    • synchronize使用比较简单,当发生异常时会自动释放锁,不会产生死锁,Lock使用不当,没有使用unLock()释放锁,则会产生死锁
    • Lock只能给代码块加锁,synchronize能给类、方法、代码块加锁
    • Lock可以知道是否获取了对象锁,而synchronize无法判断是否得到对象锁
    • 使用锁机制不同:
      • synchronize:使用了悲观锁机制,即独占锁机制,认为每个线程访问资源都会对同一资源进行修改,所以规定每次只能进入一个线程。在CPU转换线程阻塞时会导致上下文的切换,当有很多线程竞争锁时,CPU频繁的上下文切换就会导致效率将低
      • Lock:使用了乐观锁机制,认为进入的线程都不会对同一资源进行修改,如果产生冲突,就会进行重试
  22. start()和run()方法的区别有哪些?
    • start()用于启动线程,run()执行线程运行时的代码,start()只能运行一次,run()能够被多次调用
  23. 为什么wait、notify和notifyAll这些方法不在Thread类中?
    • 因为wait、notify和notifyAll都是锁级别的操作,所以这些方法都在Object类中,锁属于对象
  24. notify和notifyAll的区别?
    • notify:将随机唤醒一个线程,由JVM进行控制
    • notifyAll:将所有线程唤醒
    • 等待线程由等待池移到锁池中,之后通过锁的竞争来获取锁,获得则进入就绪状态等待分配CPU的使用权,否则就在锁定池中等待进行下一次锁的竞争,如此反复
  25. 为什么wait、notify和notifyAll方法要在同步块中调用?
    • wait()就是用来释放锁,释放锁的前提是必须拥有锁;notify()、notifyAll()是唤醒执行了wait()的线程,让其继续执行下去
      每个对象可以看作为一个“监控器monitor”,监控器分为三个部分,“独占锁、入口队列、等待队列”,每个对象只有一个对象锁,每个线程都能够拥有此对象锁,对于对象的非同步方法而言,任意时刻任意线程都都能够进行调用,而对于同步方法而言,只有获取到独占锁的线程才能进行调用方法,如果此时又另一个线程想要调用此方法,那么线程就会被阻塞,进入入口队列等待独占锁的释放;如果获取到独占锁的队列使用wait()方法的话那么此线程会释放独占锁并且进入等待队列,某个线程调用notify()、notifyAll()方法是将等待队列的线程转入入口队列,然后让线程进行竞争独占锁,所以这个调用线程就必须拥有锁,必须要在同步块中执行
  26. ThreadLocal是什么?有哪些使用场景?
    • ThreadLocal为所有线程提供线程副本,每个线程都能够独立的修改自己的线程副本,并且不会影响其他线程的副本
    • 使用场景:
      数据库的连接
      session的管理
  27. 为什么stop()和suspend()都不推荐使用?
    • stop():不安全,它会释放线程的所有锁,如果线程对象处于不连贯状态的话,那么对其他线程就是可见的就有可能检查和修改它们,且主要问题很难检测出来,我们应该在thread类中加一个标志,进行控制活动和停止。如果该标志提示停止,那么可使其结束run()。如果线程等待时间过长,应该使用interrupt()中断线程等待状态
    • suspend():容易造成死锁,因为线程调用了suspend()方法不会释放锁,其他线程不能访问到这些资源,从而导致线程阻塞。我们应该在thread中设置标志,进行控制活动和停止。如果标志提示停止,那么可使其执行wait()进入等待状态,当标志提示恢复,那么使用notify()进行唤醒
  28. 线程池中submit()和execute()方法有什么区别?
    • execute():只能执行Runnable类型的任务
    • submit():Runnable类型和Callable类型的任务都能执行
  29. atomic的原理是什么?
    • atomic主要是利用CAS(Compare And Swap 比较交换 是一种无锁机制)、volatile和native来保证原子操作,从而避免synchronize的高开销,执行效率大大提升
 
posted @ 2019-08-13 22:54  SvaloR  阅读(153)  评论(0)    收藏  举报