代码之旅

I love Coding !

在线上Java程序中经常遇到进程程挂掉,一些状态没有正确的保存下来,这时候就需要在JVM关掉的时候执行一些清理现场的代码。JDK在1.3之后提供了Java Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在以下几种场景被调用:

  1. 程序正常退出
  2. 使用System.exit()
  3. 终端使用Ctrl+C触发的中断
  4. 系统关闭
  5. 使用Kill pid命令干掉进程(kill -9|19 除外)

关闭钩子在以下情景不会被调用:

  • 通过kill -9命令杀死进程——所以kill -9|19 一定要慎用;
  • 程序中执行到了Runtime.getRuntime().halt()方法,该方法会强行关闭虚拟机;
  • 操作系统突然崩溃,或机器掉电。

一旦开始执行ShutdownHook时,无法再向JVM中addShutdownHook。

阅读全文 »

Linux信号量是一种比较原始的进程通信手段。有很多缺陷,可却是理解操作系统的基础概念。java 开放的api中涉及信号量的只有退出信号,即使用runtime.addShutdownHook()实现jvm退出钩子。但是我们可以通过 sun.misc 包来实现支持其他信号的处理。

阅读全文 »

先描述下API(Application Programming Interface )。在java中,我们使用java提供的很多类、类的方法、数据结构来编写我们的应用程序,最终完成我们需求的程序功能,这里的类、方法、数据结构即是jdk提供的api。api的意义,其实就是这些提供给你完成某项功能的类、接口或者方法。

而SPI(Service Provider Interface)是指一些提供给你继承、扩展,完成自定义功能的类、接口或者方法。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

阅读全文 »

  1. Java 的基本原理就是“形式错误的代码不会运行”。(编译错误=“形式错误的代码”)
  2. 捕获错误最理想的是在编译期间,最好在试图运行程序以前。然而,并非所有错误都能在编译期间侦测到。有些问题必须在运行期间解决,让错误的缔结者通过一些手续向接收者传递一些适当的信息,使其知道该如何正确地处理遇到的问题。

摘自 thinking in java 4th

阅读全文 »

如果匿名类的实现非常简单,比如只包含一个方法的接口,那么匿名类的语法可能看起来很笨重且不明确。使用Lambda表达式可以更简洁地表达单方法类的实例。

阅读全文 »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo {
public static void main(String[] args){
method_1();
method_2();
}
private static void method_1(){
int j = 0;
for(int i=0;i<100;i++){
j = j++;
}
System.out.println("method_1---"+j);
}
private static void method_2(){
int j = 0;
for(int i=0;i<100;i++){
j = ++j;
}
System.out.println("method_2---"+j);
}
}

上面程序的输出结果是:

1
2
method_1---0  
method_2---100

为什么会是这样呢?

因为在计算过程中,使用了Java中间变量缓存机制。在java中,执行自增运算时,会为每一个自增操作分配一个临时变量,如果是前缀加(++i),就会“先自加1后赋值(给临时变量)”;如果是后缀加(i++),就会“先赋值(给临时变量)后自加1”。运算最终使用的,并不是变量本身,而是被赋了值的临时变量。

  • j = j++; ==> {int temp = j; j = j + 1; j = temp; }
  • j = ++j; ==> {j = j + 1; int temp = j; j = temp; }

可变类和不可变类(Mutable and Immutable Objects)的初步定义:

  1. 可变类:当你获得这个类的一个实例引用时,你可以改变这个实例的内容。
  2. 不可变类:当你获得这个类的一个实例引用时,你不可以改变这个实例的内容。不可变类的实例一但创建,其内在成员变量的值就不能被修改。
  • jdk的可变类和不可变类
    • 不可变类:jdk的java.lang包中 Boolean, Byte, Character, Double, Float, Integer, Long, Short,String
    • 可变类:StringBuffer,java.util.Date
阅读全文 »

  • 修饰类,代表不可以继承扩展
  • 修饰变量,代表变量不可以修改
  • 修饰方法,代表方法不可以重写
阅读全文 »

本文介绍一些位运算技巧:

异或消除

a=0a=a00=aaa=0 \oplus a= a \oplus 0 \newline 0=a \oplus a

由上面两个推导出:

a=abba=a \oplus b \oplus b

交换两个数

a=abb=aba=aba=a \oplus b \newline b=a \oplus b \newline a=a \oplus b \newline

移除二进制最后一个1

a=n(n1)a=n \land (n-1)

获取二进制最后一个1

diff=(n(n1))ndiff=(n \land (n-1)) \oplus n

复合选项

在实际开发中常会用到选项,选项开启和关闭可以有效的控制代码执行逻辑。

对于选项,我们可以简单的用类的一个属性去判断。也可以使用一些编程技巧,通过一个数字来表示组合选项。

一个简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class ByteOption {

public enum Option {
/*
option
*/
OPTION_1(op1),
OPTION_2(op2),
OPTION_3(op3),
OPTION_4(op4),
OPTION_5(op5),
OPTION_6(op6),
OPTION_7(op7)
;

byte value;

Option(byte i) {
value = i;
}
}
// 将多个属性设置为不冲突的二进制数
public static byte op1 =0b00000001;
public static byte op2 =0b00000010;
public static byte op3 =0b00000100;
public static byte op4 =0b00001000;
public static byte op5 =0b00010000;
public static byte op6 =0b00100000;
public static byte op7 =0b01000000;
private static int ALL = 0b11111111;


private byte op = 0b00000000;

public void enableOption(Option option){
op = (byte) (op | option.value);
}

public void disableOption(Option option){
op = (byte) (op & (option.value^ALL));
}

public boolean isEnable(Option option){
return option.value == (op & option.value);
}
}

运算符<<(左移),>>(右移)和 >>>(无符号右移)称为移位运算符。移位运算符的左侧操作数是要移位的值; 右侧操作数指定移位距离。下面是语法描述:

1
2
3
4
5
ShiftExpression:
AdditiveExpression
ShiftExpression << AdditiveExpression
ShiftExpression >> AdditiveExpression
ShiftExpression >>> AdditiveExpression
  • n << s的意思是n左移s个bit位,这相当于 n2sn*2^s,注意溢出。
  • n >> s的意思是n带符号位右移s个bit位,若左操作数是正数,则高位补“0”,若左操作数是负数,则高位补“1”,这相当于 n/2sn/2^s
  • n >>>s的意思是不带符号右移,无论左操作数是正数还是负数,在高位都补“0”。
    • 如果n是正数,那么结果等同于n >> s
    • 如果n是负数,且类型是int,那么结果等同于(n >> s) + (2 << ~s)
    • 如果n是负数,且类型是long,那么结果等同(n >> s) + (2L << ~s)

按位补码运算符: ~

shift表达式的类型是左侧操作数的提升类型。

  • 如果左手操作数的提升类型是int,则只使用右手操作数的五个最低位作为移位距离.
  • 如果左侧操作数的提升类型是long,则只使用右侧操作数的六个最低位作为移位距离.
1
2
3
5 >> 32 == 5
// 5 >> 0
// 32 100000 后5位是 00000

参考