前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java面试手册:线程专题 ④

Java面试手册:线程专题 ④

作者头像
南风
发布2018-12-05 17:01:41
6680
发布2018-12-05 17:01:41
举报
文章被收录于专栏:Java大联盟Java大联盟
1、进程死锁的四个必要条件以及解除死锁的基本策略:

  • 互斥条件:线程对资源的访问是排他性的,如果一个线程对占用了某资源,那么其他线程必须处于等待状态,直到资源被释放。
  • 请求和保持条件:线程T1至少已经保持了一个资源R1占用,但又提出对另一个资源R2请求,而此时,资源R2被其他线程T2占用,于是该线程T1也必须等待,但又对自己保持的资源R1不释放。
  • 不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
  • 环路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”。
  • 解除死锁的基本策略
    • 预防死锁:通过设置一些限制条件,去破坏产生死锁的必要条件
    • 避免死锁:在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁
    • 检测死锁:允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉-解除死锁:该方法与检测死锁配合使用
2、如何避免死锁?

  • 死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:
    • 互斥条件:一个资源每次只能被一个进程使用。
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
  • 避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。这篇教程有代码示例和避免死锁的讨论细节。
  • 分析死锁,我们需要查看Java应用程序的线程转储。我们需要找出那些状态为BLOCKED的线程和他们等待的资源。每个资源都有一个唯一的id,++用这个id我们可以找出哪些线程已经拥有了它的对象锁++。
  • 避免嵌套锁,只在需要的地方使用锁和避免无限期等待是避免死锁的通常办法
3、怎么检测一个线程是否拥有锁?

  • 在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁
4、解释一下活锁:

  • 是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。释放完后,双方发现资源满足需求了,又都去强占资源,但是又只拿到一部分,就这样,资源在各个线程间一直往复。这样你让我,我让你,最后两个线程都无法使用资源。
5、Java中活锁和死锁有什么区别?

  • 活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。
6、如何确保线程安全,servlet线程安全吗?

  • 在Java中可以有很多方法来保证线程安全---同步、使用原子类(atomic concurrent classes)、实现并发锁、使用volatile关键字、使用不变类和线程安全类。
    • 使用java.util.concurrent.atomic包中的Atomic Wrapper类。例如AtomicInteger
    • 使用java.util.concurrent.locks包中的锁。
    • 使用线程安全集合类,请查看此文章以了解ConcurrentHashMap的使用情况以确保线程安全。
    • 使用带有变量的volatile关键字使每个线程从内存中读取数据,而不是从线程缓存中读取。
  • 同步是java中最简单和最广泛使用的线程安全工具,同步是我们可以实现线程安全的工具,JVM保证同步代码一次只能由一个线程执行。java关键字synchronized用于创建同步代码,在内部它使用Object或Class上的锁来确保只有一个线程正在执行同步代码
    • Java同步在锁定和解锁资源时起作用,在任何线程进入同步代码之前,它必须获取对象的锁定,并且当代码执行结束时,它解锁可以被其他线程锁定的资源。同时,其他线程处于等待状态以锁定同步资源。
    • 我们可以用两种方式使用synchronized关键字,一种是使一个完整的方法同步,另一种方法是创建synchronized块。
    • 当方法同步时,它会锁定Object,如果方法是静态的,它会锁定Class,因此最好使用synchronized块来锁定需要同步的方法的唯一部分。
    • 在创建synchronized块时,我们需要提供将获取锁的资源,它可以是XYZ.class或类的任何Object字段。
    • synchronized(this) 将在进入同步块之前锁定对象。
    • 您应该使用最低级别的锁定,例如,如果类中有多个同步块,并且其中一个锁定了Object,则其他同步块也将无法由其他线程执行。当我们锁定一个Object时,它会获取Object的所有字段的锁定。
    • Java同步提供了性能成本的数据完整性,因此只有在绝对必要时才应该使用它。
    • Java同步仅在同一个JVM中工作,因此如果您需要在多个JVM环境中锁定某些资源,它将无法工作,您可能需要考虑一些全局锁定机制。
    • Java synchronized关键字不能用于构造函数和变量。
    • 最好创建一个用于同步块的虚拟私有对象,这样它的引用就不能被任何其他代码更改。例如,如果您正在同步的Object的setter方法,则可以通过其他一些代码更改其引用,以并行执行synchronized块。
    • 我们不应该例如使用字符串不应该被用于同步的是保持在常量池中的任何对象,因为如果任何其他代码也需要在同一个String锁,它会尝试从相同的参考对象上获取锁串池和即使两个代码都不相关,它们也会相互锁定。
  • servlet不是线程安全的,每个servlet都只被实例化一次,每个调用都是servlet的同一个实例,并且对类变量没有线程安全,数据量大的时候容易造成异常
7、你对线程优先级的理解是什么?

  • 每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。
  • 我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10),1代表最低优先级,10代表最高优先级,默认为5。
  • 我们调用setPriority(),方法来设置优先级。
8、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?

  • 线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间
  • 一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
  • 时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。==分配CPU时间可以基于线程优先级或者线程等待的时间==。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(++也就是说不要让你的程序依赖于线程的优先级++)。
9、你如何确保main()方法所在的线程是Java程序最后结束的线程?

  • 我们可以使用Thread类的joint()方法来确保所有程序创建的线程在main()方法退出前结束。
  • 关于Thread类的joint()方法:
    • join()方法的作用,是等待这个线程结束,也就是说,t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程
    • Join方法实现是通过wait(Objecte提供的方法)。当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main线程调用t.join时,必须能够拿到线程t对象的锁。
10、在多线程中,什么是上下文切换(context-switching)?

  • 上下文切换是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。上下文切换是多任务操作系统和多线程环境的基本特征。
11、 Java中什么是竞态条件? 举个例子说明。

  • 当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件,竞态条件会导致程序在并发情况下出现一些bugs
  • 多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了,那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争.
  • 导致竞态条件发生的代码区称作临界区。在临界区中使用适当的同步就可以避免竞态条件。
    • 临界区实现方法有两种,一种是用synchronized,一种是用Lock显式锁实现。
    • 有临界区是为了让更多的其它线程能安全够访问资源,临界区就是修改对象状态标记的代码区
12、一个线程运行时发生异常会怎样?

  • 如果异常没有被捕获,该线程将会停止执行。
  • Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。
  • 当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。
13、如何在两个线程间共享数据?

  • 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,卖票系统就可以这么做。
  • 如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,例如,设计4个线程。其中两个线程每次对j增加1,另外两个线程对j每次减1,银行存取款
  • 有两种方法来解决此类问题
    • 将共享数据封装成另外一个对象,然后将这个对象逐一传递给各个Runnable对象,每个线程对共享数据的操作方法也分配到那个对象身上完成,这样容易实现针对数据进行各个操作的互斥和通信
    • 将Runnable对象作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个Runnable对象调用外部类的这些方法。
  • 总结:其实多线程间的共享数据最主要的还是互斥,多个线程共享一个变量,针对变量的操作实现原子性即可
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-11-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java大联盟 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、进程死锁的四个必要条件以及解除死锁的基本策略:
  • 2、如何避免死锁?
  • 3、怎么检测一个线程是否拥有锁?
  • 4、解释一下活锁:
  • 5、Java中活锁和死锁有什么区别?
  • 6、如何确保线程安全,servlet线程安全吗?
  • 7、你对线程优先级的理解是什么?
  • 8、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
  • 9、你如何确保main()方法所在的线程是Java程序最后结束的线程?
  • 10、在多线程中,什么是上下文切换(context-switching)?
  • 11、 Java中什么是竞态条件? 举个例子说明。
  • 12、一个线程运行时发生异常会怎样?
  • 13、如何在两个线程间共享数据?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档