`
niumd
  • 浏览: 288655 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

ThreadLocal示例

    博客分类:
  • java
阅读更多

    本文借花献佛,引用Tim Cull的博文“SimpleDateFormat: Performance Pig”介绍下ThreadLocal的简单使用,同时也对SimpleDateFormat的使用有个深入的了解。

Tim Cull 写道
Just yesterday I came across this problem “in the wild” for the third time in my career so far: an application with performance problems creating tons of java.text.SimpleDateFormat instances. So, I have to get this out there: creating a new instance of SimpleDateFormat is incredibly expensive and should be minimized. In the case that prompted this post, I was using JProfiler to profile this code that parses a CSV file and discovered that 50% of the time it took to suck in the file and make 55,000 objects out of it was spent solely in the constructor of SimpleDateFormat. It created and then threw away a new one every time it had to parse a date. Whew!

“Great,” you think, “I’ll just create one, static instance, slap it in a field in a DateUtils helper class and life will be good.”

Well, more precisely, life will be good about 97% of the time. A few days after you roll that code into production you’ll discover the second cool fact that’s good to know: SimpleDateFormat is not thread safe. Your code will work just fine most of the time and all of your regression tests will probably pass, but once your system gets under a production load you’ll see the occasional exception.

“Fine,” you think, “I’ll just slap a ’synchronized’ around my use of that one, static instance.”

Ok, fine, you could do that and you’d be more or less ok, but the problem is that you’ve now taken a very common operation (date formatting and parsing) and crammed all of your otherwise-lovely, super-parallel application through a single pipe to get it done.

      

     大致意思:Tim Cull碰到一个SimpleDateFormat带来的严重的性能问题,该问题主要有SimpleDateFormat引发,创建一个SimpleDateFormat实例的开销比较昂贵,解析字符串时间时频繁创建生命周期短暂的实例导致性能低下。即使将SimpleDateFormat定义为静态类变量,貌似能解决这个问题,但是SimpleDateFormat是非线程安全的,同样存在问题,如果用‘synchronized’线程同步同样面临问题,同步导致性能下降(线程之间序列化的获取SimpleDateFormat实例)。

    Tim Cull使用Threadlocal解决了此问题,对于每个线程SimpleDateFormat不存在影响他们之间协作的状态,为每个线程创建一个SimpleDateFormat变量的拷贝或者叫做副本,代码如下:

 

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * 使用ThreadLocal以空间换时间解决SimpleDateFormat线程安全问题。
 * @author 
 *
 */
public class DateUtil {
	
	private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
	
	@SuppressWarnings("rawtypes")
	private static ThreadLocal threadLocal = new ThreadLocal() {
		protected synchronized Object initialValue() {
			return new SimpleDateFormat(DATE_FORMAT);
		}
	};

	public static DateFormat getDateFormat() {
		return (DateFormat) threadLocal.get();
	}

	public static Date parse(String textDate) throws ParseException {
		return getDateFormat().parse(textDate);
	}
}

 

   创建一个ThreadLocal类变量,这里创建时用了一个匿名类,覆盖了initialValue方法,主要作用是创建时初始化实例。也可以采用下面方式创建;

 

//第一次调用get将返回null
private static ThreadLocal threadLocal = new ThreadLocal();
//获取线程的变量副本,如果不覆盖initialValue,第一次get返回null,故需要初始化一个SimpleDateFormat,并set到threadLocal中
public static DateFormat getDateFormat() 
{
	DateFormat df = (DateFormat) threadLocal.get();
	if(df==null){
		df = new SimpleDateFormat(DATE_FORMAT)
		threadLocal.set(df);
	}
	return df;
}

 

   我们看下我们覆盖的initialValue方法:

 

protected T initialValue() {
        return null;//直接返回null
    }

 

 

 

 

分享到:
评论
50 楼 jj19900703 2017-08-08  
多个pattern代码如下:

public class DateUtil {

	private static final ThreadLocal<DateFormat> normalFormat = new ThreadLocal<DateFormat>();
	private static final ThreadLocal<DateFormat> simpleFormat = new ThreadLocal<DateFormat>();
	private static final ThreadLocal<DateFormat> simpleLongFormat = new ThreadLocal<DateFormat>();
	private static final String NORMAL_FORMAT = "yyyy-MM-dd HH:mm:ss";
	private static final String SIMPLE_FORMAT = "yyyyMMddHHmmss";
	private static final String SIMPLE_LONG__FORMAT = "yyyyMMddHHmmssSSSS";

	public static enum DatePattern {
		NORMAL_FORMAT, SIMPLE_FORMAT, SIMPLE_LONG__FORMAT
	}

	private static final DateFormat getNormalFormat() {
		DateFormat df = normalFormat.get();
		if (df == null) {
			df = new SimpleDateFormat(NORMAL_FORMAT, Locale.getDefault());
			normalFormat.set(df);
		}
		return df;
	}

	private static final DateFormat getSimpleDateFormat() {
		DateFormat df = simpleFormat.get();
		if (df == null) {
			df = new SimpleDateFormat(SIMPLE_FORMAT, Locale.getDefault());
			simpleFormat.set(df);
		}
		return df;
	}

	private static final DateFormat getSimpleLongDateFormat() {
		DateFormat df = simpleLongFormat.get();
		if (df == null) {
			df = new SimpleDateFormat(SIMPLE_LONG__FORMAT, Locale.getDefault());
			simpleLongFormat.set(df);
		}
		return df;
	}

	public static Date parse(String source, DatePattern pattern) throws ParseException {
		switch (pattern) {
		case NORMAL_FORMAT:
			return getNormalFormat().parse(source);
		case SIMPLE_FORMAT:
			return getSimpleDateFormat().parse(source);
		case SIMPLE_LONG__FORMAT:
			return getSimpleLongDateFormat().parse(source);
		default:
			return getNormalFormat().parse(source);
		}
	}

	public static String format(Date date, DatePattern pattern) {
		switch (pattern) {
		case NORMAL_FORMAT:
			return getNormalFormat().format(date);
		case SIMPLE_FORMAT:
			return getSimpleDateFormat().format(date);
		case SIMPLE_LONG__FORMAT:
			return getSimpleLongDateFormat().format(date);
		default:
			return getNormalFormat().format(date);
		}
	}
}
49 楼 jj19900703 2017-08-07  
上面这些评论真是搞笑,根本没弄懂瞎评论误导人。
第一:SimpleDateFormat对象压根不能多个线程同时使用,因为它是有状态的,format和parse方法都不能在多线程情况下安全调用。所以一个线程一个SimpleDateFormat是最理想状态
第二:举个实际例子。javaweb开发。一个访问线程进来后,你可能需要在控制层创建一个SimpleDateFormat,然后逻辑层也要用到这个,那么再创建一个,然后持久层也要用到,那么继续创建一个。所以导致了很大的开销。这个时候用ThreadLocal就能解决同一个线程,不同层次之间的数据共享问题。
48 楼 myumen 2015-02-06  
helong0904 写道
在世界的中心呼喚愛 写道
helong0904 写道
zhang34082 写道

……
这个案例解决了一个系统中正真减少创建SimpleDateFormat对象的开销。好思想好思路

换到缓存也可以实现。


这个代码还是有问题,根本原因在于SimpleDateFormat对象不是线程安全的,所以不能在线程之间共享,除非加上同步机制。正确的思路应该是每个线程下,每个pattern一个SimpleDateFormat对象,这样子就解决问题了。


这里面不是已经加了同步,不过感觉如果加了同步效率不是很理想了


你的同步只是创建SimpleDateFormat对象的时候同步了,是为了多个线程使用同一个SimpleDateFormat实例。但实际上SimpleDateFormat对象压根不能多个线程同时使用,因为它是有状态的,format和parse方法都不能在多线程情况下安全调用,只是平时一般遇不到问题而已。
你试试开5个下面的线程同时运行。
class DateFormatThread extends Thread {
	/** 多线程同时访问会有问题,因为不是线程安全的 */
	private static final SimpleDateFormat sdf = new SimpleDateFormat(
			"yyyy-MM-dd HH:mm:ss");

	private static boolean runing = true;
	@Override
	public void run() {
		while (runing) {
			try {
				this.join(2000);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			try {
				System.out.println(this.getName() + ":"
						+ sdf.parse("2013-05-24 06:02:20"));
			} catch (Exception e) {
				e.printStackTrace();
				runing = false;
			}
		}
	}
}
47 楼 helong0904 2015-01-30  
在世界的中心呼喚愛 写道
helong0904 写道
zhang34082 写道
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DateUtil {

  private static final String DATE_FORMAT = "yyyy-MM-dd";

  private static Map<String, DateFormat> map = new HashMap<String, DateFormat>();
  
  private static String lock = "lock";
  
  public static DateFormat getInstance(String pattern) {
    DateFormat instance = map.get(pattern);
    if (instance == null) { 
      synchronized(lock) {            // 线程同步,只让一个线程进来实例化
        instance = map.get(pattern);  // 如果两个线程同时取到的都为null时,其中一个线程实例化后,另一个线程就不需要实例化了
        if (instance == null) { 
          instance = new SimpleDateFormat(DATE_FORMAT);
          map.put(pattern, instance);
        }
      }
    }
    
    return instance;
  }
  
  public static DateFormat getDateFormat(String pattern) {
    return getInstance(pattern);
  }

  public static Date parse(String textDate, String pattern) throws ParseException {
    if (pattern == null || pattern.equals("")) {
      pattern = DATE_FORMAT;
    }
    if (textDate == null || textDate.equals("")) {
      return null;
    }
    return getDateFormat(pattern).parse(textDate);
  }

}


对于各种不同的pattern,又保持多线程下同一个pattern共享同一个实例,以上代码可以实现,不妥之处,欢迎指正。



这个案例解决了一个系统中正真减少创建SimpleDateFormat对象的开销。好思想好思路

换到缓存也可以实现。

在世界的中心呼喚愛 写道
helong0904 写道
zhang34082 写道
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DateUtil {

  private static final String DATE_FORMAT = "yyyy-MM-dd";

  private static Map<String, DateFormat> map = new HashMap<String, DateFormat>();
  
  private static String lock = "lock";
  
  public static DateFormat getInstance(String pattern) {
    DateFormat instance = map.get(pattern);
    if (instance == null) { 
      synchronized(lock) {            // 线程同步,只让一个线程进来实例化
        instance = map.get(pattern);  // 如果两个线程同时取到的都为null时,其中一个线程实例化后,另一个线程就不需要实例化了
        if (instance == null) { 
          instance = new SimpleDateFormat(DATE_FORMAT);
          map.put(pattern, instance);
        }
      }
    }
    
    return instance;
  }
  
  public static DateFormat getDateFormat(String pattern) {
    return getInstance(pattern);
  }

  public static Date parse(String textDate, String pattern) throws ParseException {
    if (pattern == null || pattern.equals("")) {
      pattern = DATE_FORMAT;
    }
    if (textDate == null || textDate.equals("")) {
      return null;
    }
    return getDateFormat(pattern).parse(textDate);
  }

}


对于各种不同的pattern,又保持多线程下同一个pattern共享同一个实例,以上代码可以实现,不妥之处,欢迎指正。



这个案例解决了一个系统中正真减少创建SimpleDateFormat对象的开销。好思想好思路

换到缓存也可以实现。
myumen 写道
在世界的中心呼喚愛 写道
helong0904 写道
zhang34082 写道
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DateUtil {

  private static final String DATE_FORMAT = "yyyy-MM-dd";

  private static Map<String, DateFormat> map = new HashMap<String, DateFormat>();
  
  private static String lock = "lock";
  
  public static DateFormat getInstance(String pattern) {
    DateFormat instance = map.get(pattern);
    if (instance == null) { 
      synchronized(lock) {            // 线程同步,只让一个线程进来实例化
        instance = map.get(pattern);  // 如果两个线程同时取到的都为null时,其中一个线程实例化后,另一个线程就不需要实例化了
        if (instance == null) { 
          instance = new SimpleDateFormat(DATE_FORMAT);
          map.put(pattern, instance);
        }
      }
    }
    
    return instance;
  }
  
  public static DateFormat getDateFormat(String pattern) {
    return getInstance(pattern);
  }

  public static Date parse(String textDate, String pattern) throws ParseException {
    if (pattern == null || pattern.equals("")) {
      pattern = DATE_FORMAT;
    }
    if (textDate == null || textDate.equals("")) {
      return null;
    }
    return getDateFormat(pattern).parse(textDate);
  }

}


对于各种不同的pattern,又保持多线程下同一个pattern共享同一个实例,以上代码可以实现,不妥之处,欢迎指正。



这个案例解决了一个系统中正真减少创建SimpleDateFormat对象的开销。好思想好思路

换到缓存也可以实现。


这个代码还是有问题,根本原因在于SimpleDateFormat对象不是线程安全的,所以不能在线程之间共享,除非加上同步机制。正确的思路应该是每个线程下,每个pattern一个SimpleDateFormat对象,这样子就解决问题了。


这里面不是已经加了同步,不过感觉如果加了同步效率不是很理想了
46 楼 myumen 2015-01-05  
在世界的中心呼喚愛 写道
helong0904 写道
zhang34082 写道
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DateUtil {

  private static final String DATE_FORMAT = "yyyy-MM-dd";

  private static Map<String, DateFormat> map = new HashMap<String, DateFormat>();
  
  private static String lock = "lock";
  
  public static DateFormat getInstance(String pattern) {
    DateFormat instance = map.get(pattern);
    if (instance == null) { 
      synchronized(lock) {            // 线程同步,只让一个线程进来实例化
        instance = map.get(pattern);  // 如果两个线程同时取到的都为null时,其中一个线程实例化后,另一个线程就不需要实例化了
        if (instance == null) { 
          instance = new SimpleDateFormat(DATE_FORMAT);
          map.put(pattern, instance);
        }
      }
    }
    
    return instance;
  }
  
  public static DateFormat getDateFormat(String pattern) {
    return getInstance(pattern);
  }

  public static Date parse(String textDate, String pattern) throws ParseException {
    if (pattern == null || pattern.equals("")) {
      pattern = DATE_FORMAT;
    }
    if (textDate == null || textDate.equals("")) {
      return null;
    }
    return getDateFormat(pattern).parse(textDate);
  }

}


对于各种不同的pattern,又保持多线程下同一个pattern共享同一个实例,以上代码可以实现,不妥之处,欢迎指正。



这个案例解决了一个系统中正真减少创建SimpleDateFormat对象的开销。好思想好思路

换到缓存也可以实现。


这个代码还是有问题,根本原因在于SimpleDateFormat对象不是线程安全的,所以不能在线程之间共享,除非加上同步机制。正确的思路应该是每个线程下,每个pattern一个SimpleDateFormat对象,这样子就解决问题了。
45 楼 在世界的中心呼喚愛 2014-11-23  
helong0904 写道
zhang34082 写道
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DateUtil {

  private static final String DATE_FORMAT = "yyyy-MM-dd";

  private static Map<String, DateFormat> map = new HashMap<String, DateFormat>();
  
  private static String lock = "lock";
  
  public static DateFormat getInstance(String pattern) {
    DateFormat instance = map.get(pattern);
    if (instance == null) { 
      synchronized(lock) {            // 线程同步,只让一个线程进来实例化
        instance = map.get(pattern);  // 如果两个线程同时取到的都为null时,其中一个线程实例化后,另一个线程就不需要实例化了
        if (instance == null) { 
          instance = new SimpleDateFormat(DATE_FORMAT);
          map.put(pattern, instance);
        }
      }
    }
    
    return instance;
  }
  
  public static DateFormat getDateFormat(String pattern) {
    return getInstance(pattern);
  }

  public static Date parse(String textDate, String pattern) throws ParseException {
    if (pattern == null || pattern.equals("")) {
      pattern = DATE_FORMAT;
    }
    if (textDate == null || textDate.equals("")) {
      return null;
    }
    return getDateFormat(pattern).parse(textDate);
  }

}


对于各种不同的pattern,又保持多线程下同一个pattern共享同一个实例,以上代码可以实现,不妥之处,欢迎指正。



这个案例解决了一个系统中正真减少创建SimpleDateFormat对象的开销。好思想好思路

换到缓存也可以实现。
44 楼 helong0904 2014-06-20  
zhang34082 写道
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DateUtil {

  private static final String DATE_FORMAT = "yyyy-MM-dd";

  private static Map<String, DateFormat> map = new HashMap<String, DateFormat>();
  
  private static String lock = "lock";
  
  public static DateFormat getInstance(String pattern) {
    DateFormat instance = map.get(pattern);
    if (instance == null) { 
      synchronized(lock) {            // 线程同步,只让一个线程进来实例化
        instance = map.get(pattern);  // 如果两个线程同时取到的都为null时,其中一个线程实例化后,另一个线程就不需要实例化了
        if (instance == null) { 
          instance = new SimpleDateFormat(DATE_FORMAT);
          map.put(pattern, instance);
        }
      }
    }
    
    return instance;
  }
  
  public static DateFormat getDateFormat(String pattern) {
    return getInstance(pattern);
  }

  public static Date parse(String textDate, String pattern) throws ParseException {
    if (pattern == null || pattern.equals("")) {
      pattern = DATE_FORMAT;
    }
    if (textDate == null || textDate.equals("")) {
      return null;
    }
    return getDateFormat(pattern).parse(textDate);
  }

}


对于各种不同的pattern,又保持多线程下同一个pattern共享同一个实例,以上代码可以实现,不妥之处,欢迎指正。



这个案例解决了一个系统中正真减少创建SimpleDateFormat对象的开销。好思想好思路
43 楼 chenzehe 2012-11-29  

pouyang 写道
pouyang 写道
引用
每个线程第一次获取都会初始化一次,你的实践验证能力很好,可能我开始描述的不是很清晰;
每一个线程初始化一次,每一个线程自身维护一个map,空间上占用内存较多,但不需要同步节约了时间;

那把那个SimpleDateFormat对象的 static 修饰符去掉多好,
写成这样SimpleDateFormat s = new SimpleDateFormat(DATE_FORMAT);  就不需要考虑同步了,而且还不用去new ThreadLocal() ,
我的理解是用了ThreadLocal就可以跨类跨方法调用这个SimpleDateFormat 了(方法不用带参数)(robbin说的)
我是不是还没开窍啊。


我自己大概想明白了一些,如果在一个线程里面要调
public static DateFormat getDateFormat() {   
        return (DateFormat) threadLocal.get();   
 } 
这个方法,调20次的话,
写成SimpleDateFormat s = new SimpleDateFormat(DATE_FORMAT);  这样要创建很多对象,而且这是个工具类,恕我没看清楚
弄成static SimpleDateFormat s = new SimpleDateFormat(DATE_FORMAT);  并发会出问题

这个时候用了ThreadLocal的话,那么只需要在这个线程里实例化一次就ok了,而且多并发也不会出问题。



是这样的,ThreadLocal相当于保存当前线程的变量,如果一个线程调用format方法N次把DateFormat对象缓存到当前线程里就很有必要了,但是我觉得下面这个方法就没必要用synchronized来同步了:
private static ThreadLocal threadLocal = new ThreadLocal() { 
        protected synchronized Object initialValue() { 
            return new SimpleDateFormat(DATE_FORMAT); 
        } 
    };
42 楼 gtssgtss 2011-03-12  
Jony.Hwong 写道
其实楼主贴出来的使用ThreadLocal的解决方案,依然没有解决SimpleDateFormat创建开销昂贵的问题。

因为ThreadLocal的作用是:使得同一个线程共享一个实例,不同线程使用各自的实例。

对于多线程的情况,依然会创建很多个SimpleDateFormat实例。


用ThreadLocal,n个线程创建n个SimpleDateFormat,不用ThreadLocal。n个线程创建kn个SimpleDateFormat
41 楼 Jony.Hwong 2011-03-12  
其实楼主贴出来的使用ThreadLocal的解决方案,依然没有解决SimpleDateFormat创建开销昂贵的问题。

因为ThreadLocal的作用是:使得同一个线程共享一个实例,不同线程使用各自的实例。

对于多线程的情况,依然会创建很多个SimpleDateFormat实例。
40 楼 分离的北极熊 2010-09-14  
pengjunwu 写道
分离的北极熊 写道
ThreadLocal 以空间换时间
Synchornized 以时间换空间

ThreadLocal 同一个线程下确保获取的对象是唯一的,对其修改不会影响到其他线程。
其中initialValue()是一个空方法

疑问:
private static ThreadLocal<SimpleDateFormat> threadlocal = new ThreadLocal<SimpleDateFormat>(){
  protected synchronized Object initialValue(){
      reutrn new SimpleDateFormat(format);
  }
};
每个ThreadLocal在多线程下也能确保拿到的对象和其他线程不相同
而上面也提到了ThreadLocal是空间换时间,但是里面覆盖的initialValue() 却又加上了synchornized字段,这是不是有点相互矛盾?

疑问的地方在这,望解答。




加这个synchornized 就是由于SimpleDateFormat 是非线程安全 所以要同步
例如 线程a threadlocal.get()方法是这样执行 先获取当前的thread a 然后通过thread a来获取当前线程的 ThreadLocalMap(每个线程都有各自的ThreadLocalMap) 然后以threadlocal为key来get value 由于第一次获取的ThreadLocalMap 是null 此时会调用initialValue方法来new SimpleDateFormat(format) 。注意此时需要同步控制 (因为可能线程b此时也可能做new SimpleDateFormat(format)这个动作 线程a与线程b是同一个同步锁threadlocal )
注意各个线程用的key都是同一个threadlocal (threadlocal 是static 各个线程是能共享访问的)

其实这样只是保证了在线程a 的整个执行过程中 能获取到同一个SimpleDateFormat对象实例 如果有1000个线程 还是创建了1000个 SimpleDateFormat对象实例 
总结使用threadlocal只是为了避免了将这个对象作为参数传递的麻烦


既然是2个线程,而ThreadLocal是保证同一线程下获取的对象唯一,2个线程要拿到同一个对象?你看它的普通写法,也没有涉及任何同步,这就和第一个例子矛盾了

比如我现在管理Connection ,2个线程,我同事拿这一个连接,那不死的很惨。
39 楼 niumd 2010-09-12  
pouyang 写道
实在是以空间换了时间,解惑了,谢谢了
我现在积分只有20分,30分才可以投票,要不给你来个精华了

分享下自己的认识,主要在于交流,共同提高;
38 楼 pouyang 2010-09-10  
实在是以空间换了时间,解惑了,谢谢了
我现在积分只有20分,30分才可以投票,要不给你来个精华了
37 楼 pouyang 2010-09-10  
pouyang 写道
引用
每个线程第一次获取都会初始化一次,你的实践验证能力很好,可能我开始描述的不是很清晰;
每一个线程初始化一次,每一个线程自身维护一个map,空间上占用内存较多,但不需要同步节约了时间;

那把那个SimpleDateFormat对象的 static 修饰符去掉多好,
写成这样SimpleDateFormat s = new SimpleDateFormat(DATE_FORMAT);  就不需要考虑同步了,而且还不用去new ThreadLocal() ,
我的理解是用了ThreadLocal就可以跨类跨方法调用这个SimpleDateFormat 了(方法不用带参数)(robbin说的)
我是不是还没开窍啊。


我自己大概想明白了一些,如果在一个线程里面要调
public static DateFormat getDateFormat() {   
        return (DateFormat) threadLocal.get();   
 } 
这个方法,调20次的话,
写成SimpleDateFormat s = new SimpleDateFormat(DATE_FORMAT);  这样要创建很多对象,而且这是个工具类,恕我没看清楚
弄成static SimpleDateFormat s = new SimpleDateFormat(DATE_FORMAT);  并发会出问题

这个时候用了ThreadLocal的话,那么只需要在这个线程里实例化一次就ok了,而且多并发也不会出问题。
36 楼 pengjunwu 2010-09-09  
zhang34082 写道
pengjunwu 写道


hashmap本身就是非线程安全的 这个在多线程环境下还是有问题吧

你改后的代码与我的没什么区别,hashMap虽然是非线性安全的,但是在取instance时,通过synchronized保证只有一个线程进入,并实例化。
附上测试代码:

public class DateUtilTest implements Runnable {
  
  public void run() {
    try {
      System.out.println(DateUtil.parse("2008-01-04", null));  
    } catch (Exception e) {
      e.printStackTrace();
    }
    
  }

  public static void main(String[] args) {
    
    for (int i = 0; i < 5; i++) {
      Thread t = new Thread(new DateUtilTest());
      t.start();
    }
  }
}


兄弟 我的意思是这句
private static Map<String, DateFormat> map = new HashMap<String, DateFormat>();
map这个共享对象是非线程安全的 如果你在多线程操作它时会有问题的吧(如set操作)就象你定义一个全局共享的SimpleDateFormat对象一样是非线程安全的啦
private static SimpleDateFormat format= new SimpleDateFormat(“yyyy”);
35 楼 zhang34082 2010-09-09  
pengjunwu 写道


hashmap本身就是非线程安全的 这个在多线程环境下还是有问题吧

你改后的代码与我的没什么区别,hashMap虽然是非线性安全的,但是在取instance时,通过synchronized保证只有一个线程进入,并实例化。
附上测试代码:

public class DateUtilTest implements Runnable {
  
  public void run() {
    try {
      System.out.println(DateUtil.parse("2008-01-04", null));  
    } catch (Exception e) {
      e.printStackTrace();
    }
    
  }

  public static void main(String[] args) {
    
    for (int i = 0; i < 5; i++) {
      Thread t = new Thread(new DateUtilTest());
      t.start();
    }
  }
}

34 楼 pengjunwu 2010-09-09  
分离的北极熊 写道
ThreadLocal 以空间换时间
Synchornized 以时间换空间

ThreadLocal 同一个线程下确保获取的对象是唯一的,对其修改不会影响到其他线程。
其中initialValue()是一个空方法

疑问:
private static ThreadLocal<SimpleDateFormat> threadlocal = new ThreadLocal<SimpleDateFormat>(){
  protected synchronized Object initialValue(){
      reutrn new SimpleDateFormat(format);
  }
};
每个ThreadLocal在多线程下也能确保拿到的对象和其他线程不相同
而上面也提到了ThreadLocal是空间换时间,但是里面覆盖的initialValue() 却又加上了synchornized字段,这是不是有点相互矛盾?

疑问的地方在这,望解答。




加这个synchornized 就是由于SimpleDateFormat 是非线程安全 所以要同步
例如 线程a threadlocal.get()方法是这样执行 先获取当前的thread a 然后通过thread a来获取当前线程的 ThreadLocalMap(每个线程都有各自的ThreadLocalMap) 然后以threadlocal为key来get value 由于第一次获取的ThreadLocalMap 是null 此时会调用initialValue方法来new SimpleDateFormat(format) 。注意此时需要同步控制 (因为可能线程b此时也可能做new SimpleDateFormat(format)这个动作 线程a与线程b是同一个同步锁threadlocal )
注意各个线程用的key都是同一个threadlocal (threadlocal 是static 各个线程是能共享访问的)

其实这样只是保证了在线程a 的整个执行过程中 能获取到同一个SimpleDateFormat对象实例 如果有1000个线程 还是创建了1000个 SimpleDateFormat对象实例 
总结使用threadlocal只是为了避免了将这个对象作为参数传递的麻烦
33 楼 pengjunwu 2010-09-09  
zhang34082 写道
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DateUtil {

  private static final String DATE_FORMAT = "yyyy-MM-dd";

  private static Map<String, DateFormat> map = new HashMap<String, DateFormat>();
  
  private static String lock = "lock";
  
  public static DateFormat getInstance(String pattern) {
    DateFormat instance = map.get(pattern);
    if (instance == null) { 
      synchronized(lock) {            // 线程同步,只让一个线程进来实例化
        instance = map.get(pattern);  // 如果两个线程同时取到的都为null时,其中一个线程实例化后,另一个线程就不需要实例化了
        if (instance == null) { 
          instance = new SimpleDateFormat(DATE_FORMAT);
          map.put(pattern, instance);
        }
      }
    }
    
    return instance;
  }
  
  public static DateFormat getDateFormat(String pattern) {
    return getInstance(pattern);
  }

  public static Date parse(String textDate, String pattern) throws ParseException {
    if (pattern == null || pattern.equals("")) {
      pattern = DATE_FORMAT;
    }
    if (textDate == null || textDate.equals("")) {
      return null;
    }
    return getDateFormat(pattern).parse(textDate);
  }

}


对于各种不同的pattern,又保持多线程下同一个pattern共享同一个实例,以上代码可以实现,不妥之处,欢迎指正。



是不是应该这样子呀
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DateUtil {

  //private static final String DATE_FORMAT = "yyyy-MM-dd";

  private static Map<String, DateFormat> map = new HashMap<String, DateFormat>();
 
  private static String lock = "lock";
 
  public static DateFormat getInstance(String pattern) {
    DateFormat instance = map.get(pattern);
    if (instance == null) {
      synchronized(lock) {            // 线程同步,只让一个线程进来实例化
        instance = map.get(pattern);  // 如果两个线程同时取到的都为null时,其中一个线程实例化后,另一个线程就不需要实例化了
        if (instance == null) {
          instance = new SimpleDateFormat(pattern); //pattern 类似 yyyy-MM-dd 的date format
          map.put(pattern, instance);
        }
      }
    }
   
    return instance;
  }
 
  public static DateFormat getDateFormat(String pattern) {
    return getInstance(pattern);
  }

  public static Date parse(String textDate, String pattern) throws ParseException {
   
    if (textDate == null || textDate.equals("")) {
      return null;
    }
    return getDateFormat(pattern).parse(textDate);
  }

}
而且 hashmap本身就是非线程安全的 这个在多线程环境下还是有问题吧
32 楼 msi110 2010-09-09  
貌似有点像单例模式中的懒加载................
31 楼 liujun999999 2010-09-09  
zhang34082 写道
其实楼主那个例子对于多个线程访问来说,是没办法实现共享同一个实例,在ThreadLocalMap里面,是以线程为Key,也就是说多线程时,会实例化多个SimpleDateFormat。如果真的需要共享同一个实例,我觉得可以用单例模式来实现

单例模式必须同步,因为SimpleDateFormat不是线程安全的,有性能瓶颈

另外,楼主的例子有个问题,就是,SimpleDateFormat的pattern必须是唯一的,实际项目中可能需要各种各样的pattern,怎么处理这个问题?

相关推荐

    java 简单的ThreadLocal示例

    java 简单的ThreadLocal示例

    ThreadLocal应用示例及理解

    ThreadLocal应用示例及理解,这个写了相关的示例,可以参考一下。

    Android 中 ThreadLocal使用示例

    Android 中 ThreadLocal使用示例 概要: Demo描述:   ThreadLocal使用示例.  关于ThreadLocal的官方文档描述   Implements a thread-local storage, that is, a variable for which each thread has its own ...

    深入理解ThreadLocal工作原理及使用示例

    主要介绍了深入理解ThreadLocal工作原理及使用示例,涉及ThreadLocal&lt;T&gt; 简介和使用示例及ThreadLocal的原理等相关内容,具有一定参考价值,需要的朋友可以了解下。

    从面试中的问题分析ThreadLocal

    主要介绍了从面试中的问题分析ThreadLocal,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,下面我们来一起学习一下吧

    ThreadLocal源码解析

    ThreadLocal使用代码示例: public class MyThreadLocalTest { private ThreadLocal threadLocal=new ThreadLocal(){ @Override protected Integer initialValue() { return 1; } }; public void ...

    有关ThreadLocal的面试题你真的懂了吗

    主要介绍了面试题ThreadLocal,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    ThreadLocal原理及内存泄漏原因

    主要介绍了ThreadLocal原理及内存泄漏原因,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    Java ThreadLocal的设计理念与作用

    主要介绍了Java ThreadLocal的设计理念与作用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    Java单线程ThreadLocal串值问题解决方案

    主要介绍了Java单线程ThreadLocal串值问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    java-threadlocal-profiler:从 code.google.compjava-threadlocal-profiler 自动导出

    Java 线程本地分析器。 用于 Java 应用程序分析的小库。 收集同一伞下的所有性能指标,可以一起打印... 示例日志 Timestamp: Start: Elapsed: ToNext: Action: THRESHOLD ROWINDENTIFIER 2010-09-22 20:36:12.187 0

    JenkovTutorial:这是http上Jenkov教程的练习示例

    (带有ThreadLocal变量的示例)。 线程信令。 (带有4个不同的MyWaitNotify对象的2个示例)。 锁具4.1。 基本(带有2个不同的Counter对象的示例)。 4.2。 可重入(例如2个不同的可重入对象)。 Java中的读/写锁。 ...

    java线程封闭之栈封闭和ThreadLocal

    主要介绍了java线程封闭之栈封闭和ThreadLocal,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    Java并发编程学习之ThreadLocal源码详析

    主要给大家介绍了关于Java并发编程学习之源码分析ThreadLocal的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    详解Java内存泄露的示例代码

    通过一个Demo来简要介绍下ThreadLocal和ClassLoader导致内存泄露最终OutOfMemory的场景。下面通过示例代码给大家分享Java内存泄露的相关知识,感兴趣的朋友一起看看吧

    Java并发编程实战

    3.3.3 ThreadLocal类 3.4 不变性 3.4.1 Final域 3.4.2 示例:使用Volatile类型来发布不可变对象 3.5 安全发布 3.5.1 不正确的发布:正确的对象被破坏 3.5.2 不可变对象与初始化安全性 3.5.3 安全发布的常用...

    Java 并发编程实战

    3.3.3 ThreadLocal类 3.4 不变性 3.4.1 Final域 3.4.2 示例:使用Volatile类型来发布不可变对象 3.5 安全发布 3.5.1 不正确的发布:正确的对象被破坏 3.5.2 不可变对象与初始化安全性 3.5.3 安全发布的常用...

    java单例模式看这一篇就够了

    深入分析java单例模式什么是单例模式单例模式的常见写法一、饿汉式单例优点缺点示例二、懒汉式单例示例1(普通写法)示例2(synchronized写法)示例...单例示例1(容器式)示例2(枚举式)四、ThreadLocal式单例示例总结...

Global site tag (gtag.js) - Google Analytics