ThreeTen Extra 时间日期处理利器

  最近一些需求有对多个时间区间进行判断,例如交集之类的,而2个时间区间可以多达13种情况,实现起来特别容易绕晕,正好找到这样一个工具类可以满足需求,只需要一个方法便可计算出结果,很方便。ThreeTen 的设计里面 Instant 表示时间点,Interval 表示时间段,使用Interval即可对区间进行判断。

  例如:判断是否有交集(Overlaps)

Instant startA = Instant.parse("2018-08-01T00:00:00Z");
Instant stopA = Instant.parse("2018-08-10T00:00:00Z");
Instant startB = Instant.parse("2018-07-30T00:00:00Z");
Instant stopB = Instant.parse("2018-08-02T00:00:00Z");

Interval areaA = Interval.of(startA, stopA);
Interval areaB = Interval.of(startB, stopB);
boolean flag1 = areaA.overlaps(areaB);

  同样的,还有是否邻接、包含、相等、之前,之后等等。当然,除了Interval,还有别的类可以用,非常强大。官方文档也非常详细。

  官网链接:https://www.threeten.org/threeten-extra/index.html

  Maven

<dependency>
    <groupId>org.threeten</groupId>
    <artifactId>threeten-extra</artifactId>
    <version>1.4</version>
</dependency>

Java并发编程一些笔记

《Java并发编程》

  • 自旋锁与互斥锁

  两者非常类似,只是调度策略的不同。对于独占资源的访问,互斥锁在获得锁之前将一直处于休眠状态,自旋锁则是不断的自我循环来等待锁。对于线程切换没有损失,但消耗CPU,等待过长影响系统性能。

  • 并发包中的信号量与有界阻塞容器

  Semaphore用来控制对某种资源的访问数量,可以用来实现资源池化访问,也可以将任何一种容器变成有界阻塞容器。

  • 线程的关闭

  大多数时候使用原生线程都是等到运行结束而自动关闭,然而有时候也需要提前结束线程,比如用户取消了操作。但Java没有提供任何机制来安全地终止线程。仅提供了中断(Interruption),这是一种协作机制,能够使一个线程终止另一个线程(Thread.stop和suspend存在缺陷,避免使用)。

  解决方案有:非阻塞情况下使用volatile类型的变量来做标记,阻塞框架又存在可中断和不可中断,可中断调用阻塞框架中断方法,例如对阻塞队列的操作。处理好中断异常,保证数据完整性。不可中断的阻塞如IO的操作或者等待获得锁而阻塞,在取消方法中先关闭IO,或者调用Lock类的lockInterruptibly。

  • 线程饥饿死锁

  在线程池中,任务依赖其他任务,那么可能产生死锁。在单线程Executor中,一个任务将另一个任务提交到同一个Executor,并且等待这个被提交任务的结果,会死锁。

  • 线程池大小

  线程池过大,大量的线程将在相对很少的CPU和内存资源上发生竞争,导致更高的内存占用量。线程池过小导致处理器空闲,减低吞吐率。

  • synchronized与ReentrantLock

  两者jvm层语义一致,Java 6及更高版本两者效率差别已经不是很大,Lock具有公平与非公平两种选择,除本身特性之外,非公平锁吞吐率高于公平。其原因是恢复一个被挂起的线程与该线程真正开始运行之间存在较大延时。Lock具有定时锁等待,可中断锁等待,非结构化加锁。

玩转Java字节码(下)

  接上篇文章,操作字节码的框架有很多Javassist,ASM,BCEL等,这里我用ASM来举例。在了解字节码组成后,很容易通过ASM构建一个Class出来,代码如下:

package classloader;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created by dorole.com on 2016/6/13.
 */
public class HelloClassGeneratorTest {
    public static void main(String[] args) throws IOException {
        ClassWriter classWriter = new ClassWriter(0);
        classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "Hello", null, "java/lang/Object", null);

        // 构造方法
        MethodVisitor constructorMethod = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        constructorMethod.visitCode();
        constructorMethod.visitVarInsn(Opcodes.ALOAD, 0);
        constructorMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        constructorMethod.visitInsn(Opcodes.RETURN);
        constructorMethod.visitMaxs(1, 1);
        constructorMethod.visitEnd();

        // sayHello方法
        MethodVisitor helloMethod = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "sayHello", "(Ljava/lang/String;)Ljava/lang/String;", null, null);
        helloMethod.visitCode();
        helloMethod.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        helloMethod.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
        helloMethod.visitInsn(Opcodes.DUP);
        helloMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
        helloMethod.visitVarInsn(Opcodes.ALOAD, 0);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        helloMethod.visitLdcInsn(" -> ");
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        helloMethod.visitVarInsn(Opcodes.ALOAD, 1);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        helloMethod.visitVarInsn(Opcodes.ALOAD, 1);
        helloMethod.visitInsn(Opcodes.ARETURN);
        helloMethod.visitMaxs(3, 2);
        helloMethod.visitEnd();

        classWriter.visitEnd();

        byte[] data = classWriter.toByteArray();
        File file = new File("D://Hello.class");
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(data);
        fileOutputStream.close();
    }
}

  内容很简单,通过ClassWriter创建了一个Hello类,定义了一个构造方法和sayHello方法,接收一个String参数,返回String类型。将得到的Class字节码保存到D://Hello.class文件中,这个文件可以用jd-gui反编译回Java源代码。

  ASM设计模式很有意思,访问者模式(Visitor Pattern),我常常把它比作一个拥有多层的抽屉的模具,一层一层打开,放原材料,最后产出一个东西。MethodVisitor的用法可以参考文档,这里不详述,大致上都是一些字节码指令操作。

  以上代码也是模仿了javap反编译出来的内容,运行后无误的话可以看到在D盘有了一个Hello.class文件,接下来我们要加载这个文件,并运行其中的方法。代码如下:

package classloader;

/**
 * Created by dorole.com on 2016/6/13.
 */
public class MyLoader extends ClassLoader {
    public Class<?> defineMyClass(byte[] b, int off, int len) {
        return super.defineClass(null, b, off, len);
    }
}

  自定义一个Classloader,仅调用父类的defineClass即可。

package classloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by dorole.com on 2016/6/13.
 */
public class MyLoaderTest {
    public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException {
        File file = new File("D://Hello.class");
        InputStream input = new FileInputStream(file);
        byte[] result = new byte[1024];

        int count = input.read(result);
        MyLoader loader = new MyLoader();
        Class clazz = loader.defineMyClass(result, 0, count);
        System.out.println(clazz.getCanonicalName());

        Object o = clazz.newInstance();
        try {
            Method method = clazz.getMethod("sayHello", String.class);
            Object returnObject = method.invoke(o, "World!");
            System.out.println("returnObject = " + returnObject);
        } catch (IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    }
}

  将class以字节数组形式读取给Classloader来加载,通过反射调用sayHello方法,专递一个World!参数进去,可以看到输出以下结果:

Hello
Hello -> World!
returnObject = World!

  这样一个从Java代码编译与反编译,自己生成字节码到加载运行,还能反编译回去。这样一个完整的循环就算完成了,虽然谈不上很有深度,但从这里能引发很多值得思考的地方。更多内容还是要参考Java虚拟机规范。

玩转Java字节码(上)

  Java字节码(Byte-code)是指Java源代码编译而成的,供JVM虚拟机执行的代码。用文本编辑器打开将是一团乱码,用十六进制编辑器打开能勉强看懂头部一些规范,例如魔数,主次版本。而用/bin/javap“反编译”之后可以得到一个人类可读的代码段,类似于用Wireshark来分析cap数据包。

  要看懂这个文件必须要知道字节码规范,主要有以下两个表格内容构成。其中表1是Class文件的组成部分,各段依次排列,排列紧密,无多余的分隔符。其中u1~u8表示占用字节数,1表示1个字节,2表示2个字节,4字节,8字节。例如魔数:仅有1个,占用4个字节,位于文件的前4个字节。紧接着4个字节是minor_version,major_version各占用2个字节。接下来2个字节是常量池数量统计,意味着一个Class不能有超过65535个常量池(maybe)。接下来是常量池,常量池的格式由表2来定义,下一段再来分析。依次类推可以将整个Class文件分析出来。

表1Class文件组成

  常量池中的每一个常量项通常有2~3个项目组成。例如:CONSTANT_Utf8_info这一项,第1个字节是表示定义(tag),值为1,紧接着2个字节表示该项将占用的长度(length),意味着接下来的几个字节长度将是字符串的实际内容。

  再来分析下CONSTANT_Integer_info项,从下表可以看出头一个字节tag值为3,接下来4个字节安高位在前编码表示int值,所以在Java中int数据类型的最大值是去掉一个符号位的0x7FFFFFFF。Utf8,Integer,Float,Long,Double这5个都是类似的,直接以值的形式存储。其余常量项都是存储着引用值(index),分别指向这5个值或其它地方,这里不一一介绍了。

  等等java不是八大基本数据类型吗,这里怎么只有提到了4个,没错,小于int的都当作int处理了。也就是boolean,byte,char,short都“拉长”至int级别来对待。例如:boolean a = “true” 对应的字节码指令是:iconst_1,putfield a,将int型常量值1进栈赋值给变量a。

  介于编译器可能会做一些性能优化,例如int值超过2字节(32768)才会加入到常量项,小于2字节由sipush指令在运行时分配。

表2Class 14个常量项

  分析这玩意实在有些无聊,既然本文是以玩转为主题,自然就说点有趣的。首先一个简单的Java代码如下:

package classloader;

/**
 * Created by dorole.com on 2016/6/13.
 */
public class Hello {
    public String sayHello(String name) {
        System.out.println(this.getClass().getName() + " -> " + name);
        return name;
    }
}

  这段代码自然再简单不过了,随便一个文本编辑器敲进去,javac编译,找个main方法调用完事。那能不能不编译直接调用?当然没问题,先不谈这个,先来看看编译后究竟是个什么样子。我们用WinHex来打开编译好的Class文件,截图如下:

Hello.class

  黑色标记的是全部的常量项tag值(一个个标出来,该是有多蛋疼~),自己可以对照表2慢慢分析。当然也可以借助javap来“反编译”下,输出更为直观的表格:

Constant pool:
   #1 = Utf8               classloader.Hello
   #2 = Class              #1             // "classloader.Hello"
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = NameAndType        #5:#6          // "<init>":()V
   #8 = Methodref          #4.#7          // java/lang/Object."<init>":()V
   #9 = Utf8               sayHello
  #10 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #11 = Utf8               java/lang/System
  #12 = Class              #11            // java/lang/System
  #13 = Utf8               out
  #14 = Utf8               Ljava/io/PrintStream;
  #15 = NameAndType        #13:#14        // out:Ljava/io/PrintStream;
  #16 = Fieldref           #12.#15        // java/lang/System.out:Ljava/io/PrintStream;
  #17 = Utf8               java/lang/StringBuilder
  #18 = Class              #17            // java/lang/StringBuilder
  #19 = Methodref          #18.#7         // java/lang/StringBuilder."<init>":()V
  #20 = Utf8               getClass
  #21 = Utf8               ()Ljava/lang/Class;
  #22 = NameAndType        #20:#21        // getClass:()Ljava/lang/Class;
  #23 = Methodref          #4.#22         // java/lang/Object.getClass:()Ljava/lang/Class;
  #24 = Utf8               java/lang/Class
  #25 = Class              #24            // java/lang/Class
  #26 = Utf8               getName
  #27 = Utf8               ()Ljava/lang/String;
  #28 = NameAndType        #26:#27        // getName:()Ljava/lang/String;
  #29 = Methodref          #25.#28        // java/lang/Class.getName:()Ljava/lang/String;
  #30 = Utf8               append
  #31 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #32 = NameAndType        #30:#31        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #33 = Methodref          #18.#32        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #34 = Utf8                ->
  #35 = String             #34            //  ->
  #36 = Utf8               toString
  #37 = NameAndType        #36:#27        // toString:()Ljava/lang/String;
  #38 = Methodref          #18.#37        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #39 = Utf8               java/io/PrintStream
  #40 = Class              #39            // java/io/PrintStream
  #41 = Utf8               println
  #42 = Utf8               (Ljava/lang/String;)V
  #43 = NameAndType        #41:#42        // println:(Ljava/lang/String;)V
  #44 = Methodref          #40.#43        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #45 = Utf8               Code

  这里我仅放上常量池部分,这下很好对了把。Class文件就是这样一个紧凑结构,不愧是为嵌入式而打造的一门语言。回到之前的问题,有没有可能自己来生成字节码?答案是肯定的,下回分解。

轻量级ORM框架DbUtils和OrmLite

  最近在树莓派上部署了一套数据抓取工具,需要将抓取的数据录入到MySQL,特意找了下一些轻量级的ORM框架,这里简单介绍下DbUtils和OrmLite的配置和使用。

1. DbUtils

  Apache旗下的,速度与稳定性不言而喻,但其配置还是有点啰嗦,依赖dbcp连接池。

  地址:http://commons.apache.org/proper/commons-dbutils/

添加pom依赖:

<dependency>
	<groupId>commons-dbutils</groupId>
	<artifactId>commons-dbutils</artifactId>
	<version>1.6</version>
</dependency>
<dependency>
	<groupId>commons-dbcp</groupId>
	<artifactId>commons-dbcp</artifactId>
	<version>1.4</version>
</dependency>

添加dbcp.properties配置文件,放在classpath中:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.91.128:3306/db
username=steven
password=steven
initialSize=10
maxActive=50
maxIdle=20
minIdle=5
maxWait=60000
#附带连接属性
connectionProperties=useUnicode=true;characterEncoding=utf8
#自动提交(auto-commit)
defaultAutoCommit=true
#只读(read-only)
defaultReadOnly=
#事务级别(TransactionIsolation)
defaultTransactionIsolation=READ_COMMITTED

新增JdbcUtil.java来加载属性文件

package com.dorole.utils;

import java.io.InputStream;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

import com.mysql.jdbc.Connection;

public class JdbcUtil {
	private static DataSource ds;
	private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

	static {
		try {
			Properties prop = new Properties();
			InputStream in = JdbcUtil.class.getClassLoader()
					.getResourceAsStream("dbcp.properties");
			prop.load(in);
			ds = BasicDataSourceFactory.createDataSource(prop);
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	public static DataSource getDataSource() {
		return ds;
	}

	public static Connection getConnection() throws SQLException {
		try {
			Connection conn = tl.get();
			if (conn == null) {
				conn = (Connection) ds.getConnection();
				tl.set(conn);
			}
			return conn;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

插入数据:

QueryRunner runner = new QueryRunner(JdbcUtil.getDataSource());
String sql = "INSERT INTO `orders` (`storeName`, `telecomNumber`) VALUES (?, ?)";
Object params[] = { order.getStoreName(), order.getTelecomNumber(), };
try {
	runner.update(sql, params);
} catch (SQLException e) {
	e.printStackTrace();
}

2. OrmLite

  这个在Android上用的比较多,结合轻量级的数据库HSQLDB,非常适合嵌入式设备,但这里我们使用MySQL做演示,有意思的是连BaseDao都给封装好了,配置简单,易于使用。

  地址:http://ormlite.com/

添加pom依赖:

<dependency>
	<groupId>com.j256.ormlite</groupId>
	<artifactId>ormlite-jdbc</artifactId>
	<version>4.9</version>
</dependency>

新增OrderDao接口,默认的增删改查已经有了,故可以留空:

package com.dorole.dao;

import com.dorole.model.Order;
import com.j256.ormlite.dao.Dao;

public interface OrderDao extends Dao<Order, Integer> {

}

新增OrderDaoImpl实现类

package com.dorole.dao.impl;

import java.sql.SQLException;

import com.dorole.dao.OrderDao;
import com.dorole.model.Order;
import com.j256.ormlite.dao.BaseDaoImpl;
import com.j256.ormlite.support.ConnectionSource;

public class OrderDaoImpl extends BaseDaoImpl<Order, Integer> implements
		OrderDao {
	public OrderDaoImpl(ConnectionSource connectionSource,
			Class<Order> dataClass) throws SQLException {
		super(connectionSource, dataClass);
	}
}

初始化:

OrderDao orderDao = null;
ConnectionSource connectionSource;
try {
	connectionSource = new JdbcConnectionSource(
			"jdbc:mysql://192.168.91.128:3306/db");
	((JdbcConnectionSource) connectionSource).setUsername("steven");
	((JdbcConnectionSource) connectionSource).setPassword("steven");
	orderDao = new OrderDaoImpl(connectionSource, Order.class);
} catch (SQLException e) {
	e.printStackTrace();
}

插入数据:

try {
	Order order = new Order();
	orderDao.create(order);
} catch (SQLException e) {
	e.printStackTrace();
}