邦仁聊技术技术问答集锦@IT·互联网

☆技术问答集锦(二)

2016-07-09  本文已影响547人  七寸知架构

4 如何通过JDK命令,分析排查死锁、死循环?

top 命令:显示当前的活动进程,默认它是按消耗 CPU 的厉害程度进行排序,每5秒钟刷新一次列表,你也可以选择不同的排序方式,例如 m 是按内存占用方式进行排序的快捷键。

命令:top -Hp pid

top -Hp pid

可以实时的跟踪并获取指定进程中最耗cpu的线程。再用 jstack方法提取到对应的线程堆栈信息

jps 命令:

jps用来查看JVM里面所有进程的具体状态, 包括进程ID,进程启动的路径等等。

jstack 命令:

  1. 查看java程序崩溃生成core文件,获得core文件的java stack和native stack的信息;
  2. 查看正在运行的java程序的java stack和native stack的信息:a) 查看运行的java程序呈现hung的状态;b) 跟踪Java的调用栈,剖析程序。

线程的状态分析:

Runnable:该状态表示线程具备所有运行条件,在运行队列中准备操作系统的调度,或者正在运行

Wait on condition:该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合 stacktrace来分析。最常见的情况是线程在等待网络的读写;另外一种出现 Wait on condition的常见情况是 该线程在 sleep,等待 sleep的时间到了时候,将被唤醒

Waiting for monitor entry 和 in Object.wait():在多线程的 JAVA程序中,实现线程之间的同步,就要说说 Monitor。 Monitor是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor

jinfo 命令:

jinfo可观察运行中的java程序的运行环境参数:参数包括Java System属性和JVM命令行参数;也可从core文件里面知道崩溃的Java应用程序的配置信息。

jstat 命令:

jstat利用了JVM内建的指令 对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控等等。

jmap 命令:

观察运行中的jvm物理内存的占用情况,包括Heap size, Perm size等等。

4.1 Java死循环分析

其实这个地方按CPU的使用率来判定还不太好理解,以运行时间来判定可能更能说明问题些。

4.2 Java死锁分析

在多线程程序的编写中,如果不适当的运用同步机制,则有可能造成程序的死锁,经常表现为程序的停顿,或者不再响应用户的请求。 比如在下面这个示例中,是个较为典型的死锁情况:

较为典型的死锁情况

5 Java中j.u.c包原理实现及CAS算法?

5.1 Java的多线程同步机制

在现代的多处理器系统中,提高程序的并行执行能力是 有效利用 CPU 资源的关键。为了有效协调多线程间的并发访问,必须采用适当的同步机制来协调竞争。当前常用的多线程同步机制可以分为下面三种类型:

volatile 变量:轻量级多线程同步机制,不会引起上下文切换和线程调度。仅提供内存可见性保证,不提供原子性。

CAS 原子指令:轻量级多线程同步机制,不会引起上下文切换和线程调度。它同时提供内存可见性和原子化更新保证。

内部锁和显式锁:重量级多线程同步机制,可能会引起上下文切换和线程调度,它同时提供内存可见性和原子性。

在这里,CAS 指的是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。简单介绍一下这个指令的操作过程:首先,CPU 会将内存中将要被更改的数据与期望的值做比较。然后,当这两个值相等时,CPU 才会将内存中的数值替换为新的值。否则便不做操作。最后,CPU 会将旧的数值返回。这一系列的操作是原子的。它们虽然看似复杂,但却是 Java 5 并发机制优于原有锁机制的根本。简单来说,CAS 的含义是“我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少”。

在轻度到中度的争用情况下,非阻塞算法的性能会超越阻塞算法,因为 CAS 的多数时间都在第一次尝试时就成功,而发生争用时的开销也不涉及线程挂起和上下文切换,只多了几个循环迭代。没有争用的 CAS 要比没有争用的锁便宜得多(这句话肯定是真的,因为没有争用的锁涉及 CAS 加上额外的处理),而争用的 CAS 比争用的锁获取涉及更短的延迟。

5.2 Java中j.u.c包原理实现

Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此 任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

  1. 首先,声明共享变量为volatile;
  2. 然后,使用CAS的原子条件更新来实现线程之间的同步;
  3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

concurrent包实现示意图

6 有锁与无锁的本质区别?

有锁、无锁的本质都是在解决并发情况下竞态资源的线程安全问题。无锁只是把“排他性”进一步的弱化,以提高并发量,最大限度的使用CPU

无锁只会在CPU控制权切换的等待,而有锁会在“锁释放”、“CPU控制权切换”两种情况下的等待。

最终无锁情况下,CPU使用率上不去,吞吐量下降,就受系统资源限制了。即使线程量增加,无非增加的是线程CPU控制权的切换成本,统一受CPU的控制调度,出现的也只能是等待了。

7 volatile关键字是否是线程安全的?

volatile关键字仅保证两点:

  1. volatile变量线程之间可见性;
  2. 禁止针对volatile变量操作指令重排序;

但最重要一点没有保证,关系到线程安全,就是Volatile变量操作指令的不保证原子性问题

什么是原子操作:

多个线程执行一个操作时,其中任何一个线程要么完全执行完此操作的步骤,要么就没有执行此操作的任何步骤,那么认为这个操作才是原子的。

关于指令重排序的问题,在单线程中,重排序的前后只要不影响JVM执行结果,JVM可以运行指令重排序

指令重排序的意义在于:

JVM能够根据处理器特性(CPU多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器性能。

但是 在指令重排序在多线程的情况下,重排序的前后结果,可能不会一致了。这里导致结果不一致主要是线程安全的问题,即使指令不重排序,也会存在线程安全问题。这也就是原子性问题、或者没有加锁的问题

上一篇下一篇

猜你喜欢

热点阅读