【问题标题】:Best Design Pattern for handling general methods which will be accessed by multiple threads at a time处理将由多个线程一次访问的一般方法的最佳设计模式
【发布时间】:2016-04-19 00:40:09
【问题描述】:

我试图创建一个静态方法,一次可以被不同类的多个方法访问。

由于它是静态的,它会被锁定,我会得到不同的结果和性能问题。

有没有更好的方法可以让多个方法同时访问这些方法?

在 Spring Framework 中有没有更好的方法来处理这个问题?

  public class TestUtil {

  public static SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");

   public static java.sql.Date getLastDayOfMonth(String month,intyear) 
    throws ParseException{      
    switch (month) {
    case "jan": 
             return new java.sql.Date(f.parse(year+"-1-31").getTime());
    case "feb": if(isLeapYear(year))
             return new java.sql.Date(f.parse(year+"-2-29").getTime());
             else
              return new java.sql.Date(f.parse(year+"-2-28").getTime());
    case "mar":                
             return new java.sql.Date(f.parse(year+"-3-31").getTime());

    case "apr": 
             return new java.sql.Date(f.parse(year+"-4-30").getTime());
    case "may": 
             return new java.sql.Date(f.parse(year+"-5-31").getTime());
    case "jun": 
             return new java.sql.Date(f.parse(year+"-6-30").getTime());
    case "jul": 
             return new java.sql.Date(f.parse(year+"-7-31").getTime());
    case "aug": 
             return new java.sql.Date(f.parse(year+"-8-31").getTime());
    case "sep": 
             return new java.sql.Date(f.parse(year+"-9-30").getTime());
    case "oct": 
             return new java.sql.Date(f.parse(year+"-10-31").getTime());
    case "nov": 
             return new java.sql.Date(f.parse(year+"-11-30").getTime());
    case "dec": 
             return new java.sql.Date(f.parse(year+"-12-31").getTime());        
    default: month = "Invalid month";
    return null;  
}           
}
}

【问题讨论】:

  • 是什么让您认为多线程是这里的性能瓶颈?顺便说一句,SimpleDateFormat isn't thread safe:“日期格式不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一个格式,则必须在外部同步。”

标签: java multithreading spring design-patterns


【解决方案1】:

为安全起见,您可以做的最好的事情之一是消除多个线程的状态更改,最确定的方法是使用不可变对象。

它经常被忽视,但SimpleDateFormat 具有可变状态(它跟踪调用之间的解析操作状态)。因此,正如您所发现的,从多个线程同时使用它是行不通的。

寻找替代方案来消除共享状态。在这种情况下,很简单:只需使用 Java 8 中改进的日期和时间 API:

private static final DateTimeFormatter f; 

static {
  DateTimeFormatterBuilder b = new DateTimeFormatterBuilder();
  b.parseCaseInsensitive();
  b.parseDefaulting(ChronoField.DAY_OF_MONTH, 1);
  b.appendPattern("uuuuMMM");
  f = b.toFormatter(Locale.US); /* f is an immutable object! */
}

public static LocalDate getLastDayOfMonth(String month, int year)
{
  LocalDate first = LocalDate.parse(year + month, f);
  LocalDate last = first.with(TemporalAdjusters.lastDayOfMonth());
  return last;
}

【讨论】:

  • 谢谢,如果我必须使用 Java7 并且必须在 java.sql.Date 中返回日期怎么办?
  • @CorkKochi 然后在每次调用该方法时创建一个Calendar 的新本地实例。或者使用 Joda Time。
【解决方案2】:

社论

你的技术很糟糕。

回答

使用 Spring 将实例化对象注入到您的类中。 默认类型是单例, 这就是你想要的。 不要在您的实用程序方法中存储任何状态。 使用非静态方法。

不要解析字符串来查找日期, 而是使用 Calendar 类。 此外,您可以很容易地没有静态 SimpleDateFormat 而只使用本地。 这是一些代码(使用静态 SimpleDateFormat)。

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class Dates
{
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

    private static Date getLastDay(final int month, final int year)
    {
        final Calendar calendar = Calendar.getInstance();
        Date returnValue;

        switch (month)
        {
            case Calendar.DECEMBER:
                calendar.set(Calendar.YEAR, year + 1);
                calendar.set(Calendar.MONTH, Calendar.JANUARY);
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                calendar.add(Calendar.DAY_OF_YEAR, -1);

                returnValue = calendar.getTime();

                break;

            case Calendar.JANUARY:
                calendar.set(Calendar.YEAR, year);
                calendar.set(Calendar.MONTH, Calendar.FEBRUARY);
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                calendar.add(Calendar.DAY_OF_YEAR, -1);

                returnValue = calendar.getTime();
                break;

            case Calendar.FEBRUARY:
                calendar.set(Calendar.YEAR, year);
                calendar.set(Calendar.MONTH, Calendar.MARCH);
                calendar.set(Calendar.DAY_OF_MONTH, 1);
                calendar.add(Calendar.DAY_OF_YEAR, -1);

                returnValue = calendar.getTime();
                break;

            default:
                returnValue = new Date();
                break;
        }

        return returnValue;
    }

    public static void main(
        String[] args)
    {
        Date date;

        System.out.println("Dec 2015: " + dateFormat.format(getLastDay(Calendar.DECEMBER, 2015)));
        System.out.println("Jan 2015: " + dateFormat.format(getLastDay(Calendar.JANUARY, 2015)));
        System.out.println("Feb 2015: " + dateFormat.format(getLastDay(Calendar.FEBRUARY, 2015)));
        System.out.println("Feb 2016: " + dateFormat.format(getLastDay(Calendar.FEBRUARY, 2016)));
    }

}

【讨论】:

  • 如果我必须返回 java.sql.Date 而不是 java.util.date 怎么办
  • 我建议您阅读 java.sql.Date 和 java.util.Date 类的 API 文档。 java.sql.Date 可以用很长的毫秒构造。 java.util.Date 有一个方法 (getDate()) 以毫秒为单位返回 Date 值。 returnValue = new java.sql.Date(calendar.getTime().getTime());
  • 私有静态最终 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");这不是线程安全的吗?stackoverflow.com/questions/5652518/…
  • 仅用于格式化时,我相信 SimpleDateFormat 是线程安全的。考虑阅读您引用的答案,不管你信不信,.parse 和 .format 是不同的方法。
【解决方案3】:

如果您绝对需要一个静态字段,请将其更改为ThreadLocal<U>,在您的情况下为public static ThreadLocal<SimpleDateFormat> f = new ThreadLocal<SimpleDateFormat>();,或者,如果不需要,请在您的静态方法中将字段更改为局部变量,您将很好。

【讨论】:

  • ThreadLocal 对象和局部变量存储在堆栈内存中。但是,如果您想在线程之间共享状态(原子性),则需要另一种方法。
猜你喜欢
  • 2017-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-09
相关资源
最近更新 更多