前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小课堂|SimpleDateFormat简单池化保安全

小课堂|SimpleDateFormat简单池化保安全

作者头像
孟君
发布2021-10-13 11:41:55
3590
发布2021-10-13 11:41:55
举报

我们知道,SimpleDateFormat不是线程安全的,为了保证多线程下安全使用,我们一般采用如下几种方式:

代码语言:javascript
复制
1、每次都新建SimpleDateFormat
2、SimpleDateFormat对象的方法增加synchronized修饰
3、使用ThreadLocal
4、借助Apache Common Lang包的FastDateFormat
5、借助Joda Time包下的DateTimeFormatter
6、采用Java8 推出的日期处理类LocalDate

有兴趣的读者的可以看早期的文章SimpleDateFormat线程不安全示例及其解决方法

本文采用池化思想,完成一个简单的Pool,复用对象的同时,保证SimpleDateFormat在多线程环境下的安全使用。

1、SimplePool的实现

定义变量

为实现简单的Pool,定义几个简单的变量:

1、对象池大小

代码语言:javascript
复制
//对象池大小
 private final int poolSize;

2、存放池对象的数组

代码语言:javascript
复制
  //存放池对象的数组
  private Object[] poolObjs;

3、创建池对象的工厂

代码语言:javascript
复制

  public interface Factory {
    public Object create();
  }

  //创建池对象的工厂
  private final Factory factory;

4、用于多线程获取对象和放回对象互斥的lock对象

代码语言:javascript
复制
 // 互斥对象
  private Object LOCK = new Object();

5、下一个可用对象的数组下标

代码语言:javascript
复制
  //下一个可用对象的下标
  private int nextAvailableIndex;
 

有了这些简单的变量之后,我们就可以采用构造函数初始化变量,如:

代码语言:javascript
复制
  public SimplePool(Factory factory) {
    //默认对象池大小为5
    this(5, factory);
  }

  public SimplePool(int poolSize, Factory factory) {
    this.poolSize = poolSize;
    this.factory = factory;
    poolObjs = new Object[poolSize];
  }

接下来我们只要完成fetchFromPool和putInPool的方法即可。

fetchFromPool方法

该方法的实现步骤如下:

  • 1、调用方法使用synchronized(LOCK)锁住
  • 2、判断线程池是否有可用的对象,判断逻辑为nextAvailableIndex == poolSize,如果相等意味着当前没有可用的对象,则调用LOCK.wait()等待
  • 3、使用result = poolObjs[nextAvailableIndex++]获取对象,如果获取的对象还没有初始化,则调用factory创建一个对象,并放入到数组中。
代码语言:javascript
复制
public Object fetchFromPool() {
    Object result;
    synchronized (LOCK) {
      /**
       * 1、判断线程池是否都被占用
       */
      while (nextAvailableIndex == poolSize) {
        // 等待
        try {
          LOCK.wait();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }

      /**
       * 2、从线程池获取对象
       */
      result = poolObjs[nextAvailableIndex++];
      if (result == null) {
        result = factory.create();
        this.putInPool(result);
        ++nextAvailableIndex;
      }

    }
    return result;
  }

putInPool方法

该方法的实现步骤如下:

  • 1、调用方法使用synchronized(LOCK)锁住
  • 2、调用poolObjs[--nextAvailableIndex] = object赋值给数组
  • 3、调用LOCK.notify()通知一个线程执行
代码语言:javascript
复制
  protected void putInPool(Object object) {
    synchronized (LOCK) {
      poolObjs[--nextAvailableIndex] = object;
      LOCK.notify();
    }
  }

SimplePool完整代码如下

代码语言:javascript
复制
public class SimplePool {

  public interface Factory {
    public Object create();
  }

  //对象池大小
  private final int poolSize;

  private final Factory factory;

  public SimplePool(Factory factory) {
    //默认对象池大小为5
    this(5, factory);
  }

  public SimplePool(int poolSize, Factory factory) {
    this.poolSize = poolSize;
    this.factory = factory;
    poolObjs = new Object[poolSize];
  }

  // 互斥对象
  private Object LOCK = new Object();
  //存放池对象的数组
  private Object[] poolObjs;
  //下一个可用对象的下标
  private int nextAvailableIndex;

  public Object fetchFromPool() {
    Object result;
    synchronized (LOCK) {
      /**
       * 1、判断线程池是否都被占用
       */
      while (nextAvailableIndex == poolSize) {
        // 等待
        try {
          LOCK.wait();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }

      /**
       * 2、从线程池获取对象
       */
      result = poolObjs[nextAvailableIndex++];
      if (result == null) {
        result = factory.create();
        this.putInPool(result);
        ++nextAvailableIndex;
      }

    }
    return result;
  }

  protected void putInPool(Object object) {
    synchronized (LOCK) {
      poolObjs[--nextAvailableIndex] = object;
      LOCK.notify();
    }
  }

}

2、PooledSimpleDateFormat的实现

有了SimplePool,并且已经保证了对象获取和放回的安全性。所以一个使用SimplePool完成SimpleDateFormat的操作就很简单了,本文取名为PooledSimpleDateFormat。

初始化Pool

代码语言:javascript
复制
  private final SimplePool pool;

  public PooledSimpleDateFormat(String formatString, int poolSzie) {
    this(formatString, poolSzie, false);
  }

  public PooledSimpleDateFormat(String formatString, int poolSzie, final boolean lenient) {
    pool = new SimplePool(poolSzie, new SimplePool.Factory() {
      public Object create() {
        SimpleDateFormat dateFormat = new SimpleDateFormat(formatString);
        dateFormat.setLenient(lenient);
        return dateFormat;
      }

    });
  }

parse和format都使用pool中的对象,用完放回去即可

代码语言:javascript
复制
  
  private DateFormat fetchFromPool() {
    DateFormat format = (DateFormat) pool.fetchFromPool();
    return format;
  }

  public String format(Date date) {
    DateFormat format = fetchFromPool();
    try {
      return format.format(date);
    } finally {
      // 用完放回去
      pool.putInPool(format);
    }
  }

  public Date parse(String date) throws ParseException {
    DateFormat format = fetchFromPool();
    try {
      return format.parse(date);
    } finally {
      pool.putInPool(format);
    }
  }

完整代码

代码语言:javascript
复制
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public final class PooledSimpleDateFormat {

  private final SimplePool pool;

  public PooledSimpleDateFormat(String formatString, int poolSzie) {
    this(formatString, poolSzie, false);
  }

  public PooledSimpleDateFormat(String formatString, int poolSzie, final boolean lenient) {
    pool = new SimplePool(poolSzie, new SimplePool.Factory() {
      public Object create() {
        SimpleDateFormat dateFormat = new SimpleDateFormat(formatString);
        dateFormat.setLenient(lenient);
        return dateFormat;
      }

    });
  }
  
  private DateFormat fetchFromPool() {
    DateFormat format = (DateFormat) pool.fetchFromPool();
    return format;
  }

  public String format(Date date) {
    DateFormat format = fetchFromPool();
    try {
      return format.format(date);
    } finally {
      // 用完放回去
      pool.putInPool(format);
    }
  }

  public Date parse(String date) throws ParseException {
    DateFormat format = fetchFromPool();
    try {
      return format.parse(date);
    } finally {
      pool.putInPool(format);
    }
  }


}

3、PooledSimpleDateFormat的使用

PooledSimpleDateFormat可以直接使用或者封装在Util工具中使用,都是可以的。如:

代码语言:javascript
复制
private static final PooledSimpleDateFormat PSDF = new PooledSimpleDateFormat("yyyy-MM-dd", 10);

//调用 PSDF.parse("2021-09-28")

或者

代码语言:javascript
复制
import java.text.ParseException;
import java.util.Date;

public final class MyUtils {

  private static final PooledSimpleDateFormat SAFE_DATE_FORMAT = new PooledSimpleDateFormat("yyyy-MM-dd", 10);

  private MyUtils() {
  }

  public static String format(Date date) {
    return SAFE_DATE_FORMAT.format(date);
  }

  public static Date parse(String date) throws ParseException {
    return SAFE_DATE_FORMAT.parse(date);
  }

}


//调用 MyUtils.parse("2021-09-28");

测试一下

代码语言:javascript
复制
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Main {
  
  private static final PooledSimpleDateFormat PSDF = new PooledSimpleDateFormat("yyyy-MM-dd", 10);

  public static void main(String[] args) throws Exception {

    //format测试
    format();
    
    //parse测试
    parse();
  }
  

  private static void format() throws Exception {

    int availableProcessors = Runtime.getRuntime().availableProcessors();
    ExecutorService exec = Executors.newFixedThreadPool(availableProcessors);

    List<Future<String>> results = new ArrayList<>();
    Callable<String> parseDateTask = new Callable<String>() {
      public String call() throws Exception {
        return MyUtils.format(new Date());
      }
    };
    
    for (int i = 0; i < 50; i++) {
      results.add(exec.submit(parseDateTask));
    }
    exec.shutdown();

    /**
     * 查看结果
     */
    for (Future<String> result : results) {
      System.out.println(result.get());
    }
  }
  
  private static void parse() throws Exception{

    int availableProcessors = Runtime.getRuntime().availableProcessors();
    ExecutorService exec = Executors.newFixedThreadPool(availableProcessors);

    List<Future<Date>> results = new ArrayList<>();

    Callable<Date> parseDateTask = new Callable<Date>() {
      public Date call() throws Exception {
        //return MyUtils.parse("2021-09-29");
        return  PSDF.parse("2021-09-29");
      }
    };

    for (int i = 0; i < 50; i++) {
      results.add(exec.submit(parseDateTask));
    }

    exec.shutdown();

    /**
     * 查看结果
     */
    for (Future<Date> result : results) {
      System.out.println(result.get());
    }
  }
}

执行结果:

代码语言:javascript
复制
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
2021-09-29
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021
Wed Sep 29 00:00:00 CST 2021

我们可以看到,一个简单的对象池完成的SimpleDateFormat线性安全的示例就完成了。

4、SimplePool使用JUC的lock

在上述示例中,我们采用了synchronized + wait 和notify来完成。在Java中,我们完全可以使用J.U.C并发包的Lock和ReentrantLock完成操作。微调一下代码即可,如:

代码语言:javascript
复制
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SimplePool2 {

  public interface Factory {
    public Object create();
  }

  private final int poolSize;
  private final Factory factory;

  public SimplePool2(int poolSize, Factory factory) {
    this.poolSize = poolSize;
    this.factory = factory;
    poolObjs = new Object[poolSize];
  }

  private Lock lock  = new ReentrantLock();
  
  private Object[] poolObjs;
  private int nextAvailableIndex;

  public Object fetchFromPool() {
    Object result;
    lock.lock();
    
    try {
      
      /**
       * 1、判断线程池是否都被占用
       */
      while (nextAvailableIndex == poolSize) {
        // 等待
        try {
          lock.wait();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }

      /**
       * 2、从线程池获取对象
       */
      result = poolObjs[nextAvailableIndex++];
      if (result == null) {
        result = factory.create();
        this.putInPool(result);
        ++nextAvailableIndex;
      }
    }finally {
      lock.unlock();

    }
    return result;
  }

  protected void putInPool(Object object) {
    lock.lock();
    try {
      poolObjs[--nextAvailableIndex] = object;
    }finally {
      lock.unlock();
    }
  }

}

同样,也能正常运行。

5、小结

本文给出了一个简单的、自定义的对象池的思想,来完成SimpleDateFormat的parse和format操作,能够在多线程的环境下成功测试调用。但是,没有考虑资源的回收、Dsicard策略等等。

有兴趣的读者,可以去进一步实现~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-09-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟君的编程札记 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、SimplePool的实现
  • 2、PooledSimpleDateFormat的实现
  • 3、PooledSimpleDateFormat的使用
  • 4、SimplePool使用JUC的lock
  • 5、小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档