Kris's Blog

分享


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

同步和Java内存模型

发表于 2016-09-01   |   分类于 翻译   |  

原文地址:http://gee.cs.oswego.edu/dl/cpj/jmm.html
声明: 个人英文水平有限,翻译的不对的地方请重拍!
Doug Lee的书:Concurrent Programming In Java的网上地址:http://gee.cs.oswego.edu/dl/cpj/index.html
References: http://www.infoq.com/cn/articles/java-memory-model-1

Java-Memory-Mode

概述

考虑下面的不带同步的一个小类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final class SetCheck {
private int a = 0;
private long b = 0;

void set() {
a = 1;
b = -1;
}

boolean check() {
return ((b == 0) ||
(b == -1 && a == 1));
}
}
  • 在一个单纯的顺序执行的语言中,这个方法check永远不可能返回false。 即使编译器、运行时系统甚至硬件都有可能会用一种违反直觉的方式执行这段代码,这个结果也不会改变。例如,以下的任何一种情形都可能是方法set的执行情况:

    1. 编译器有可能会重排语句的执行顺序,所以:b有可能会在a之前被赋值。如果这个方法是内联的,编译器甚至还有可能会根据其他的语句的执行情况来重排序当前的语句。
    2. 处理器也可能会重排序这些语句对应的机器码的执行顺序,甚至有可能是同时执行。
    3. 内存系统(由缓存控制单元管理)可能会对提交给变量对应的内存单元的写操作进行重排序。这些写操作可能会和其他的计算和内存操作重叠。
    4. 编译器、处理器和内存系统都可能会交错影响这两句代码在机器码层面的执行。 例如,在一个32位的机器上,变量b的高阶位的字符可能会先被写入,然后是写入变量a,最后才是写入b的低位字符。
    5. 编译器、处理器和内存系统也可能会将变量对应的内存单元的值放置在某个能让代码有同样执行效果的非内存区域的地方(如CPU寄存器),直到后续的对变量触发校验动作的时刻才会被真正的更新到主存。
  • 在一个单纯的顺序执行的语言中,以上任何一种情况只要代码遵循as-if-serial顺序执行语义都不会有问题,顺序执行的程序不依赖于代码块内部的底层处理细节,所以他们很好的应对以上任意一种执行情况,这就为编译器和底层系统提供了很好的伸缩性,充分利用这些便利和机会使得过去的十年间计算机的执行速度得到了大幅度的提升。遵循as-if-serial顺序执行语义的所有的这些控制方法使得程序员可以不需要知道底层具体发生了什么,那些从来没有创建过线程的程序员们对这些变化和影响几乎不会有任何的感知。

  • 但是在并发编程中事情是完全不同的。这种情形下,完全有可能set是在一个线程中被调用而check是在另一个线程中被执行,也就是说check可能在监听另一个线程中set的执行情况。并且前面说的任何一种控制手段的执行都有可能会导致check返回false,例如下面会描述的细节中,check会读取一个既不是0也不是-1的值给b,而是一个中间的半写入的值。同样的,set方法中的乱序执行可能导致check方法读取到的b是-1然后读取到a还是0。
  • 换句话说,不单单是并发执行可能会导致执行的错乱,它们同样也可能会被重排序或为了获得某种形式的优化提升而做一些几乎和源代码没有任何关联的的优化操作。随着编译器和运行时技术的成熟和多核处理器的普及,这种现象也越加的普遍,这对那些只具备单线程顺序执行编程背景并且从来没有接触过底层的所谓的顺序执行代码的程序员来说,程序往往会出现让他们很吃惊的结果。这有可能就是那些狡猾的并发编程错误的根源。
  • 在几乎所有可能的情况下,有一种很显然的简单的方式去避免由于代码执行优化机制导致的在并发编程中的复杂的问题,那就是使用同步。 例如,如果SetCheck类中的两个方法都声明成synchronized,你就可以确认不会有任何的底层的处理细节会影响代码最终的想要的执行结果。
  • 但有时候你不能或不想使用同步,又或者也许由于其他人的代码没有使用同步,在这些情形下,你务必要依赖于Java内存模型规定的关于执行结果的最小保证的语义。这个模型允许上面列出的各种的底层操作,但是模型对这些底层操作对执行语义的潜在的影响做了限制,同时提供给程序员一些附加的技术手段去控制这些语义。
  • Java内存模型是JavaTM语言规范的一部分,主要讲解时在JLS的第17章。 在这里,我们只讨论这个模型基础的使用动机、相关属性和编程影响。
  • 我们可以把底层的内存模型假想成如下图的一个理想化的标准的SMP machine图:
    1. 为便于理解这个模型,我们设想每个线程都运行在一个单独的CPU上。即便在多核的机器上,实际使用中这也是很少见的,但事实上为了让线程占用一些内存模型的初始的属性,这种一个线程一个CPU的匹配方式是合理的且符合这个内存模型的。例如,由于CPU的寄存器不可以别其他的CPU直接访问,这就使得模型就必须允许一个线程感知不到被其他线程操作的的变量的值。但是内存模型的这种影响绝不仅仅限于多核系统,即便在单核CPU上编译器和处理器的一些操作也会产生和多核上一样的担忧和问题。
    2. 这个模型并没有明确的指出前面列出的各种底层的执行策略具体的是被编译器、CPU、缓存控制器获取其他的机制所执行的。它甚至没有以程序员们熟悉的类、对象或方法的形式介绍过。取而代之的是,这个模型是定义了一个线程和主存之间的抽象的关系。每个线程都有一个工作内存**working memory** (是一个缓存和寄存器的抽象概念)用来存储值。模型保证了围绕方法顺序执行交互环节的一些属性和变量的内存地址信息。大部分规则的措辞都是规定需要在什么时候应该对主内存和线程工作内存进行交换操作,这些规则产生了以下的一些交织的问题:
      • 原子性,这条说明需要有不可分割的效果,为达到模型的要求,这些规则只应该对相应的实例变量、实例、静态变量、数组元素(不包括方法本地变量)的简单的读和写适用。
      • 可见性,在什么条件下一个线程的效果对其他的线程是可见的。这些效果包含:对一个实例变量的写入被另外一个线程的读取操作看见。
      • 顺序性,在什么情况下代码的执行顺序对任意的一个线程来说是乱序的。主要的顺序性问题是发生在读取和写入一些关联的顺序的执行一些赋值语句的变量的时候。

        如果是使用同步,所有这些属性都都有一个简单的角色。在一个同步方法或者块内的所有的更新对其 他的持有同一个锁的同步方法和同步块都是原子的和可见的。并且任何线程内的同步方法和同步块的 执行都是顺序的。尽管程序块内的语句执行有可能是乱序的,但是这个对其他的使用同步的线程来说 不会有影响。

    3. 当程序未使用同步或者同步不一致

Java并发结构

发表于 2016-08-29   |   分类于 翻译   |  

原文地址:http://gee.cs.oswego.edu/dl/cpj/mechanics.html
声明: 个人英文水平有限,翻译的不对的地方请重拍!
Doug Lee的书:Concurrent Programming In Java的网上地址:http://gee.cs.oswego.edu/dl/cpj/index.html

线程

  1. 线程是一个可以彼此间独立执行、同时共享底层系统资源(如文件、共享对象)的调用序列,Thread类是控制和记录线程活动的类。
  2. 每个应用至少包含一个线程(即启动JVM的那个线程), 其他的内部后台线程也会在JVM初始化的时候启动,线程数量和特性随各个JVM不同会有很大差异,但是,所有的用户线程都是由主线程(或者说它们的父线程)明确构建和启动的。
  3. 下面是一些对线程Thread类相关的重要方法、属性以及使用注意事项的总结, 所有这些都会在本书的其他章节进行讨论和解读。 针对JLS和公开的相关API文档应当咨询更详细和权威的描述。

构造

  1. 不同的构造方法接受不同组合的构造参数如下:

    • 一个Runnable对象,这种情况下,接下来调用Thread.start()方法会调用Runnable对象的run()方法,默认的Thread本身就是实现了Runnable接口的对象,只不过默认的run方法是空;
    • 一个标示线程的字符串,这个可以用来追踪和调试,除此之外没什么其他用处;
    • 接受一个ThreadGroup,新线程都会放在这个线程组里,注意,如果没有访问当前线程组的权限,会抛出SecurityException异常;
  2. Thread类自身实现了Runnable接口,所以除了构造函数里传入一个Runnable对象,你还可以通过继承Thread类实现一个带有run方法的子类的方式来实现一个线程。但是最好的策略还是定义一个单独的Runnable接口的实现类并作为构造函数的参数传递给Thread类,因为:

    • 在一个单独的Runnable实现类里面实现代码逻辑可以免去潜在的Thread类和Runnable实现类中的同步代码块和同步方法交互的问题。 更具普遍意义的是,这种类型的代码分离有助于我们对具体的处理过程和对应的线程上下文做单独的控制。
    • 同时,针对同一个Runnable可以提供给多个有不同实例化方式的线程,也可以提供给其他轻量级的线程池框架处理。
    • 继承Thread类的方式会使得子类无法继承其他的类,不利于扩展。
  3. Thread类有一个可以通过构造函数设置的daemon属性(但是只能在一个线程启动之前设置)。

    • setDaemon方法的意思是设置当前线程为后台驻留线程,JVM会判断,当所有的非daemon线程都结束时,立即停止所有daemon线程并退出JVM。
    • isDaemon方法会返回当前线程是否是daemon线程,这个方法的作用不大,即便后台线程在程序退出时经常需要做一些清理工作(daemon读成day-mon)。

线程启动

  1. 调用start方法后,会触发Thread实例启动一个独立的活动流去执行实例的run方法。 调用线程(父线程)所持有的同步锁不会被新的线程持有。
  2. 当线程的run方法无论是正常返回还是抛出一个未受检异常(如RuntimeException),线程都会终止。 线程即便终止之后也是==不可重启==,调用start方法多次会抛出InvalidThreadStateException异常。
  3. isAlive方法返回true代表线程已经启动但还没有终止。 如果当前线程阻塞了,该方法会返回true,在这个点上不同的JVM实现会有不同,有的JVM在线程被取消cancelled的情况下会返回false。没有方法可以判断一个是is not alive状态的线程是否曾经被启动过,同样的,一个线程也不能很轻易的判断出是由哪个父线程启动的,虽然可以知道其他在同一个线程组ThreadGroup里面的线程是谁。
1
2
3
4
5
6
//ThreadPoolExecutor.java
//runWorker(Worker w)方法
while (task != null || (task = getTask()) != null) {
//这里会循环判断队列里面的任务数是否为空,不为空的情况下,线程池里面的线程一直是为终止状态,达到池化得效果
}
processWorkerExit(w, completedAbruptly);//如果队列里面的任务数为空,在这里面释放线程

优先级

  1. 为了可以使得JVM实现跨越不同硬件平台和操作系统,Java语言对线程调度和公平性不做保证,甚至不严格保证线程会一直执行。但是线程是支持通过启发式的设置线程优先级方法来影响线程调度器的执行

    • 每个线程都有一个优先级,优先级序号处在Thread.MIN_PRIORITY 和 Thread.MAX_PRIORITY之间。
    • 默认请情况下,每一个新线程都拥有和其创建线程一致的优先级。初始执行main方法的线程默认情况下的优先级为Thread.NORM_PRIORITY。
    • 可以通过getPriority方法获取任意线程的当前优先级。
    • 可以通过setPriority方法动态设置任意线程的优先级,最大优先级由其所在的线程组的大小限定。
  2. 当存在超过可用CPU核心数的可执行线程时,CPU线程调度器更倾向于优先执行高优先级的线程。

    具体的策略在不同的平台可能会有不同,例如,一些JVM的实现总是会选择最高优先级的线程执行,其他一些JVM会匹配线程的十个优先级到一些系统支持的更小(<10)的优先级类别,这样就会使得不同优先级的线程有可能会被JVM当做同等优先级对待。其他一些混淆声明的优先级或其他的调度策略会保证即便低优先级的线程最终也会有机会得到执行。 同样的,由于计算机系统中其他应用的存在,设置JVM线程的优先级,可能会,但不一定会影响调度器的执行策略。

  3. 优先级并不承载其他的计算机语义和正确性方面的义务。

    尤其是不能用优先级控制来替代线程执行中的锁,优先级只能被用来表示不同线程间的重要性和紧急程度,在线程间竞争获取执行机会非常激烈的的场合下优先级会显得非常有用。程序应该优先按照运行正确的设计理念来设计,即便设置优先级的方法setPriority被定义为无操作的方法.

  4. 下面的表格列出了一些约定俗成的优先级设置策略的任务类型。 在存在很多并发场景的应用中,相对来说只有非常少的一部分线程在任何时候都是可执行状态(其他的线程都由于各种原因被阻塞了), 这种场合下控制线程的优先级显得没有多大意义。其他的并发系统的场景中,微小的优先级设置的变化会对最终的执行产生影响。

Range Use Remark
10 Crisis management 危机处理,最高
7-9 Interactive, event-dirven 交互,事件驱动
4-6 IO-bound IO类型
2-3 Background computation 后台运行
1 Run only if noting else on 仅当其他线程都不执行的情况下

控制方法

  1. 只有很少的几个方法可以用来做线程间的交互:

    • 每个线程都有一个关联的boolean变量interruption status标示出线程的中断状体。

      调用线程的interrupt方法会把线程的中断状态设置成true,除非线程正处以下方法的执行状态中:Object.wait(),Thread.sleep(),Thread.join(),这些情况下interrupt()方法会导致线程抛出异常InterruptException,但是线程的interrupt状态会设置为false。

    • 任何线程的中断状态都可以通过isInterrupt方法来检测。

      如果是通过调用interrupt方法来中断线程的话,该方法会返回true,
      但是status状态为false,因为无论是通过调用Thread.interrupted方法还是处在
      Object.wait(),Thread.sleep(),Thread.join()等方法的处理中系统都会抛出中断异常
      InterruptException。

    • 调用thread.join()方法会将调用线程挂起suspend,并等待目标线程thread完成。

      当线程的thread.isAlive()返回false时,thread.join()会立即返回。还有一个带有超时时间版本的join(time)方法,这个方法会在超时时间之后强制返回,即便线程还有没有处理完成。由isAlive()方法的定义可以看出,对一个还未开始的线程join()是没有任何意义的。同样的理由join一个不是你创建的线程也是不明智的。

  2. 最开始的时候,Thread类是支持一些额外的控制方法的,如suspend,stop,resume,destroy。现在suspend,resume,stop方法已经被废弃了,destroy方法从来就没在任何的发行版本的JDK中实现过,以后估计也不会了。

    现在可以通过wating|notification技术实现和suspend|resume方法一样的效果,并且更加安全,后续还会围绕stop方法产生的问题继续展开讨论。

静态方法

  1. 一些线程的方法被设计成只适用于当前运行的线程(例如,调用这些方法的的线程,即当前线程)。 为了强化这个意义,这些方法被定义成静态方法

    • Thread.currentThread方法返回一个当前线程的引用,这个引用随后可以用于调用其他的非静态方法会。
    • Thread.interrupted方法会清除当前线程的中断状态并返回之前的状态(这也说明,一个线程的中断状态不可能被其他的线程清除)。
    • Thread.sleep(long millseconds)方法会导致当前线程挂起一段时间。
  2. Thread.yield方法仅仅是提示虚拟机如果有其他的可执行但是不在执行中的线程存在,线程调度器应该优先调度运行这些线程。但是不同的虚拟机可能对这个操作提示有任意的不同的解读。

    尽管没有强制的保证,yield方法在一些不使用时间分片的提前抢占式调度策略的单核CPU的JVM实现版本中可能会非常有效果。在这种情况下,只有当一个线程阻塞了(执行IO或者sleep),其他线程才有可能会被重新调度,在这些系统中,执行耗时的非阻塞计算的线程会一直占用线程执行周期,降低应用的响应响应速度。作为一个安全保护机制,执行非阻塞的可能会超过时间处理器的可接受的响应时间的计算的线程或者其他的响应式线程可以插入执行yield方法(甚至执行sleep),当然同样可以设置低的优先级,来让出CPU的执行时间。为了尽可能的减少不必要的影响,你还可以偶尔的时不时的调用一下yield方法。

  3. 在其他一些拥有预抢占式策略的虚拟机实现中,尤其是对于多核CPU来说,调度器可能甚至是提倡忽略yield方法给出的提醒。

线程组

  1. 每一个线程都是作为一个线程组的成员来构造的,默认情况下,这个线程组就是调用线程构造器的线程所在的线程组,线程组嵌套类似于树状结构。 当一个对象构造一个新的线程组时,这个线程组是嵌套在当前线程组下的,getThreadGroup方法返回任何线程的线程组。
  2. 设计线程组的目的是用来支持动态地限制对线程访问操作的安全策略。

    例如,interrupt中断一个和当前线程不是同一个线程组的线程是非法的,这个是虚拟机保护机制的一部分,有些问题,例如一个applet想杀掉一个主屏幕的显示更新线程,通过限制不同线程组之间的访问权限可以阻止它们发生。
    线程组同样可以设置一个最大的优先级,所有组内的线程都不能超过这个优先级。

  3. 不提倡直接将线程组用于线程编程模型中,大部分的应用中,为达到独立于应用的目的,使用一般的集合类来追踪一组线程对象是更好的选择。

  4. 在并发编程中极少用到的几个线程组的方法中,有一个方法uncaughtException, 这个方法是当一个线程由于抛出一个未受检异常的时候调用,这个方法一般是会打印出异常栈。

同步

对象和锁

  1. 每个Object和其子类的对象实例都拥有一个锁。
  2. 基础类型如:int,float,long等并不是对象,只有通过他们的包装类才能被锁住。
  3. 单独的变量不能被同步关键字修饰。
  4. 锁操作只能在方法内使用。
  5. 被volatile关键字修饰的的字段,执行时会在原子性、可见性、和顺序执行上得到保障。
  6. 基础数据类型的数组对象可以持有锁,但是数组内单个的元素是不能的。
  7. Class实例是对象,和Class对象关联的锁一般被用在静态方法中。

同步方法和同步块

  1. synchronized关键字有两种语法形式,同步方法和同步代码块。

    同步块有一个对象参数,这个对象就是需要锁定的对象。
    最常用的同步块参数是this当前对象。

  2. 同步关键字不作为方法签名的一部分。

    所以同步修饰关键字在重写父类的方法时不会自动的继承
    并且接口内的方法无法用同步修饰符修饰,构造方法也不可以用同步修饰符修饰

  3. 同步修饰的子类实例方法和父类拥有同样的锁。但是内部类的同步方法和外部类是不同的锁。然而,一个非静态内部类方法可以锁住其外部类。
1
2
3
4
5
6
7
8
private Class Inner{
public void test(){
synchronized(OuterClass.this) {
/* body */
System.out.println("test Inner sync")
}
}
}

获取和释放锁

  1. 当使用sychronized关键字的时候,锁操作遵循一个获取和释放协议。

    所有的锁操作都是块结构的,只有当要进入一个同步块或者同步方法的时候才会需要获取锁,退出的时候释放锁,即便是由于异常导致的退出也不能忘记释放锁的操作。

  2. 锁操作是基于一个线程维度的,不是针对每个调用来说的。

    当一个线程抵达同步临界点,如果线程本就持有该对象的锁或者该对象的锁没有被其他线程持有,就占有锁并执行通过,否则就阻塞当前的线程执行。
    重入锁和递归锁和默认的POSIX线程默认的锁策略不同。
    这种机制对于同样的一个对象,允许一个同步方法针对同样的锁定对象调用另外一个同步方法。

  3. 针对同一个对象,不同线程的同步块同步方法之间遵循同样的对锁的获取和释放协议。即便一个同步方法在执行,另一个线程也可以同时调用同一个对象的其他非同步方法。也就是说,同步不等于原子操作,但是同步可以用来实现原子操作。
  4. 当一个线程释放一个锁的时候,其他线程就可以获取到这个锁(有可能是同一个线程哦,如果线程释放后立即进入另外一个同步方法中)。但是对于接下来哪个线程会获取到锁或什么时候一个线程能获取到锁虚拟机对此不作保证。 也即是没有公平性的保证,同样的,也没有一种机制去确定对于一个给定的锁,当前正在被哪个线程锁持有。
  5. 接下来会讨论到,除了控制锁,同步同样也对底层的内存模型有副作用。

静态

  1. 锁定一个对象的意思不是说会对该对象的类和父类的静态字段做访问限制。要对静态字段做访问限制需要通过静态方法和静态块来实现。静态同步锁是通过类对象关联的静态方法来实现的。

    类C的静态锁也可以通过以下方式在实例方法中访问:synchronized(C.class) { /* body */ }

  2. JVM内部获取和释放类对象的锁是在类加载和初始化的阶段之间完成的,使用普通类方法和类对象的同步块是不会影响这些JVM的内部机制的,除非是你自己写的一个特殊的类加载器或是你在静态序列初始化阶段同时持多个类对象的锁。

    没有其他的内部JVM的动作会单独的为你使用和创建的类获取锁。但是如果你的子类是java.*包的,你 需要注意在这些包中的类的锁的策略。

  3. 静态锁关联的类和其他类包括其父类都是不相关的。想通过在子类中增加一个静态方法来实现对父类中静态字段的访问现在是无效的。

    推荐使用明确的类名的静态块实现方式代替getClass()的方式

1
2
3
4
5
synchronized(C.class) { /* 推荐使用 */ }
synchronized(getClass()) {
/* 不推荐使用,这里其实锁的是实际运行时的类,
不是你想要的类 */
}

监视器

  1. 同每个对象都有一把锁一样,每个对象都有一个只能被这些方法:wait, notify, notifyAll and Thread.interrupt操控的线程等待集合。同时持有锁和等待集合的对象一般统称为:监视器。

    大部分的其他语言对这个的细节定义都有所不同。java中,任何对象都可以作为一个监视器。

  2. 每一个对象的等待集合都是在内部被JVM操控的。每个集合中都包含了一个被wait方法阻塞住的线程列表,只有当其他线程调用了对象的通知方法或wait被释放了之后这些线程才有可能继续执行。

  3. 由于对象的等待集合和同步锁的交互方式决定了,这些方法wait, notify, and notifyAll只能在对象的同步锁被占有的情况下才能被调用。

    这些约定机制无法在编译期由编译器去校验,所以如果在运行时不遵从这个机制的话会抛出一个运行时的 异常IllegalMonitorStateException。

  4. 这些方法的执行解释如下:

    • wait 线程T执行这个方法会产生下列影响:
      1. 如果当前线程被打断,则这个方法会立即退出,抛出InterruptedException异常,否则该方法一直阻塞。
      2. JVM会把这个线程放在内部(也即是不提供给外部访问)的和目标对象关联的一个等待集合中。
      3. 目标对象的同步锁被释放,但是所有该线程下持有的其他的锁还是会继续持有。即便这个目标对象的锁由于嵌套同步调用的原因被重入,也照样会被释放,在后者恢复后,锁的状态会被完全重置。
    • notify 线程K执行这个方法会有下列影响:
      1. 如果在该对象的监视器等待集合中存在线程,JVM任务选择一个线程T并从等待集合中移除。如果等待集合中存在超过一个的线程的话,JVM对具体选择哪个进行移除操作不做保证。
      2. 被选择的线程T必须重新去竞争获取目标对象的同步锁,这样总是会导致线程T阻塞一直到调用notify的线程K释放目标对象的锁为止。这期间如果其他的线程P先抢占到这个锁的话,阻塞会一直继续。
      3. 最后如果线程T获取到对象的锁,就会从等待的执行点唤醒恢复执行。
    • notifyAll 这个方法和notify方法的工作机制是一样的,不同之处是,这个方法是针对等待集合中的所有线程都有效果,但是同样的,由于需要竞争获取目标对象的同步锁,所以实际上是一次一个线程执行的。
    • interrupt
      1. 如果一个线程正在挂起等待状态,这时候调用Thread.interrupt方法,这种情况会产生和notify机制同样的反应,只不过在重新获取到锁之后会抛出一个InterruptedException异常,并且线程的的中断状态会被置成false。
      2. 如果interrupt和notify在同一时间发生,JVM不保证哪一个会先得到执行,所以两个结果都是有可能的(以后的Java语言规范(JLS)可能对这种情况的结果会有一个确定的保证)。
    • timed waits 带过期时间的wait方法

      • 带过期时间的wait方法:wait(long msecs), wait(long msecs, int nanosecs)会在设置的最大时间内将线程维持在等待队列中。这个的执行效果和不带时间限制的wait方法的执行效果是基本一致的,只是说,带过期时间的wait方法会在过期时间到之后如果还没有被notify的话,等待线程会自动被从等待集合中释放。这两个版本的方法并没有其他的状态上的区别。
      • 超时时间版的wait方法会在超时时间到之后,随机的一个时间点被唤醒,这是由于线程竞争和CPU调度策略以及定时器的时间粒度等决定的。(对于定时器的时间粒度的影响,JVM并不给出保证,我们观察到的大部分的JVM是:当时间参数设置的小于1毫秒的情况下,响应时间大约在1-20毫秒之内)。
      • Thread.sleep(long msecs)方法内部其实使用的是wait(long msecs)方法,但是这个睡眠方法并没有绑定到当前同步块或者同步方法对应的对象的锁。它的实现可以用以下代码来展现:

        1
        2
        3
        4
        if (msecs != 0)  {
        Object s = new Object();
        synchronized(s) { s.wait(msecs); }
        }

        当然了,各个系统不必非得按照这种方式实现sleep方法,同时请注意,sleep(0)的意思是线 程至少暂停0毫秒,鬼知道这是什么意思!

Java阻塞队列(BlockingQueue)

发表于 2016-08-15   |   分类于 数据结构   |  

队列 Queue

队列是专门设计为存储需要优先处理的对象的集合, 队列中的集合操作方法有两种形式: 1:抛出异常,2:返回特定值

异常 特定值 解释
add(e) offer(e) 插入
remove(e) poll() 删除
element() peek 获取队列头部元素,但是不删除

记录一次踩坑

发表于 2016-06-25   |   分类于 Java   |  

atom

记录一次去年底的踩坑过程

起因介绍

业务系统正在稳当的跑着,结果半夜报警电话不停,机器load报警,起来看到cpu使用率很低,报警是由于内存导致的,非常奇怪本身系统的逻辑很简单,
就是保险系统的生单和出保逻辑对外提供一套dubbo的api服务。

逐层分析

按理推断即便系统有问题有也应该是业务系统应该报警才对,怎么会是机器在报警,为了验证自己的想法,
去看tomcat的gc日志,和自己预期一致,没有发现有fullgc, 系统日志也没有OOM异常,那就奇怪,4g内存,
jvm申请3g最大的堆内存,按道理机器不应该报内存不够啊,折腾了半天还是没有发现有什么异常的地方,
但是load的报警还在持续,回到刚刚的gc日志,忽然意识到一个问题居然一天之内一次fullgc都没发现,
很奇怪啊,按道理系统稳定运行一段时间后,fullgc应该是按照一个稳定的频率出现才对,
到服务器上sudo jstat -gcutil pid看了下gc统计日志,居然真是一次都木有啊

推理演变

把自己收集到的素材整理下:

  • 系统没有FullGC
  • tomcat 内存足够
  • 虚机load报警

这么说来推测一下,一定是有什么地方申请了一块内存但是没有释放,最起码是System.gc()没起效果

如果系统本身一直是在年轻代进行垃圾回收,那就意味着一定不会触发fullGC, 通过观察gc日志和对系统本身使用的
了解,我推断是由于使用了堆外内存导致的, 公司所有的中间件里面底层都是依赖netty来构建的,dubbo,asynchttp,
而系统本身就是犹如一个高速公路一样,只是做数据的传输工作,应用本身基本上没有本地缓存,而且强依赖外部接口(保险平台),
dubbo,asynchttp这些中间件的使用率非常频繁,

netty本身是会显式的调用System.gc()进行垃圾回收的,再联想到公司默认的jvm配置-XX:+DisableExplicitGC,这就不奇怪了,
本身如果系统自己有fullGC的话,那就是系统自身的fullGC来回收,但是这条路也不同,这就导致来堆外内存没有释放,

所以结论是: netty不停的申请堆外内存,而又得不到释放,导致系统load压力一致在涨

验证

-XX:+DisableExplicitGC 将这个参数去掉, 观察系统10分钟,sudo jstat -gcutil pid 发现有fullGC了, 系统的load慢慢也下来了

批评自己

其实上面的验证方法不太符合工程学实现的要求, 我们是通过推理来验证自己的想法, 还应该讲tomcat的gc dump出来,通过分析工具分析系统的哪一个线程和代码会有可能有问题,这才是比较科学的分析方法

Alfred介绍

发表于 2016-06-09   |   分类于 DevTools   |  

/images/Alfred-icon.jpg

以前只是听说alfred比mac自带的搜索工具spotlight要强大很多,下载下来发现其实也没体验到有多强大,最近这几天闲来无聊,把玩mac的时候尝试了下alfred的其他功能,不得不说太强大了,so powerful !

Alfred is an award-winning app for Mac OS X which boosts your efficiency with hotkeys, keywords, text expansion and more. Search your Mac and the web, and be more productive with custom actions to control your Mac.

基础功能

  • 打开应用程序, 覆盖了spotlight的所有功能, and there is more
  • 简单搜索,直接在输入框里面输入你需要的关键词,回车即提交Google搜索
  • 定位文件,ctrl-space 呼出Alfred后,键入空格,输入你要查找文件名,即可定位文件,回车打开,command+回车打开文件所在文件夹
  • 复杂操作文件:通过find、open、in等关键词搜索文件。find是定位文件,open是定位并打开文件,in是在文件中进行全文检索,三种检索方式基本上可以找到任何你想找的文件
  • 操作Shell:输入>即可直接运行shell命令。比如> cat *.py | grep print,可以直接打开终端并查找当前py文件中包含 print 的语句。
  • 控制itunes播放,输入iTunes,会出现一个 iTunes mini play,打开可以通过 Alfred 控制音乐播放。用快捷键也能完成这个功能:shift+option+command+p
  • 输入email,后面跟邮件地址,可以直接打开写邮件的界面
  • 定义文字片段和粘贴板,在 Alfred 的设置-Features 选中Clipboard,在Snippets里定义自己常用的文字片段,比如代码、地址等等等,之后以option+command+l (这个快捷键是我自己定义的,原先的和其他的快捷键有冲突)呼出界面,输入文字片段的关键字回车即可。这里可以保存很长时间的粘贴板的内容,赞!!!
  • 自定义搜索,这个稍微复杂些,打开设置窗口,点击Features-Custom Search,在右侧栏添加自定义搜索。举几个例子帮助大家理解下规则:

    • 搜索iOS App:

      1
      2
      3
      Search URL:
      itunes://ax.search.itunes.apple.com/WebObjects/MZSearch.woa/wa/search?term={query}
      Title:iOS App
    • 搜索Mac app:

      1
      2
      3
      4
      Search URL:
      macappstore://ax.search.itunes.apple.com/WebObjects/MZSearch.woa/wa/search?q={query}
      Title: mac app
      keywords: mac

workflow 插件

alfred给予了用户很大的自由度去对其进行扩展,这就是workflow插件机制,用户可以按照一定的开发规范,开发各自的任务插件,比如打电话、发短信、播放音乐,搜索天气等等…是不是非常的强大!

With Alfred’s Powerpack and workflows, you can extend Alfred and get things done in your own way. Replace repetitive tasks with workflows, and boost your productivity.

先看看我的workflow

myalfredworkflow

有非常多的这种workflow可以供我们下载使用:当然我们也可以去Google,一搜一大堆,或者直接安装一个叫workflow search的workflow,直接在alfred上搜,下面提供两个比较常用的搜索地址:
https://www.alfredapp.com/workflows
http://www.alfredworkflow.com

workflow search

这个插件可以随时直接在搜索框中搜索可用的workflow,并下载,关键字是:wf
workflow_search

baidu weather

直接查看当前城市的天气状况,关键字:tq
baidu_weather

air quality

查看当前城市的空气质量, 关键字: air
air_quality

copy path

这个workflow 直接做成快捷键的方式,在finder里面选中文件后,直接快捷键:ctrl-shift-c 即可拷贝当前文件的绝对路径
copy_path

有道翻译

直接在搜索框中翻译,自动转换中英文, 关键字: yd
youdao

知乎日报

直接查看知乎日报的热门文章列表, 关键字: zh
zhihudaily

人民币金额大写

直接转换人民币金额的大写中文, 关键字: cny
cny

stackoverflow 搜索

直接搜索stackoverflow上的相关答案信息,关键字: .so
stackoverflow

电话呼叫

直接呼叫iphone上的联系人: 关键字: call 这个貌似不支持电话薄的查询,需要每次都输入电话号码,比较麻烦
call
calling

evernote 搜索

搜索印象笔记中的内容,同时可以新建笔记,
关键字:en 搜索笔记,ennew 新建笔记
evernote

百度地图搜索

搜索百度地图,
关键字: setl: 设置当前城市, bmap 定位具体地点
baidumap

结语

alfred 的一般基本功能已经能满足日常大部分的使用了,而且比spotlight要好用的多,付费的powerpak-workflow进一步提升了这个工具的逼格,把玩过后,我只想说,不要太牛掰啊!

12
Kris Wan

Kris Wan

You'll live through your pain.Know it will pass,And strength you will gain

9 日志
6 分类
8 标签
RSS
GitHub Twitter Weibo Linkedin
© 2018 Kris Wan
由 Hexo 强力驱动
主题 - NexT.Mist
本站访客数人次 本站总访问量次