`
niumd
  • 浏览: 288821 次
  • 性别: 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
    }

 

 

 

 

分享到:
评论
30 楼 zhang34082 2010-09-09  
其实楼主那个例子对于多个线程访问来说,是没办法实现共享同一个实例,在ThreadLocalMap里面,是以线程为Key,也就是说多线程时,会实例化多个SimpleDateFormat。如果真的需要共享同一个实例,我觉得可以用单例模式来实现
29 楼 pengjunwu 2010-09-09  
pengzhoushuo 写道
pengjunwu 写道
不明白 如果format 有2种或者更多 怎么处理
String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; 
String DATE_FORMAT = "yyyy/MM/dd HH:mm:ss"; .....

如果你有两个format,那就建多一个ThreadLocal变量呀,楼主想要表达的意思是说不要每次都new一个SimpleFormat,同个线程可以共享一个SimpleFormat。

我的意识是说 DATE_FORMAT 不能作为参数动态来创建SimpleFormat。
28 楼 pouyang 2010-09-09  
引用
每个线程第一次获取都会初始化一次,你的实践验证能力很好,可能我开始描述的不是很清晰;
每一个线程初始化一次,每一个线程自身维护一个map,空间上占用内存较多,但不需要同步节约了时间;

那把那个SimpleDateFormat对象的 static 修饰符去掉多好,
写成这样SimpleDateFormat s = new SimpleDateFormat(DATE_FORMAT);  就不需要考虑同步了,而且还不用去new ThreadLocal() ,
我的理解是用了ThreadLocal就可以跨类跨方法调用这个SimpleDateFormat 了(方法不用带参数)(robbin说的)
我是不是还没开窍啊。
27 楼 niumd 2010-09-09  
pouyang 写道
不好意思刚代码好像有点问题,但是这样还是输出三次

import java.text.DateFormat;
import java.text.SimpleDateFormat;
public class T {
	 private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";   
	 private static ThreadLocal threadLocal = new ThreadLocal() {   
	        protected synchronized Object initialValue() {   
	        	System.out.println("-----");
	            return new SimpleDateFormat(DATE_FORMAT);   
	        }   
	    };   
	    public static void main(String[] args) {
	    	  Thread t1 = new Thread(new Runnable() {   
	                public void run() {   
	                    T t = new T();   
	                    DateFormat df = (DateFormat) threadLocal.get();   
	                    if(df==null){   
	                        df = new SimpleDateFormat(DATE_FORMAT);   
	                        threadLocal.set(df);   
	                    } 
	                   //threadLocal.get();   
	                }   
	            });   
	            Thread t2 = new Thread(new Runnable() {   
	            	   public void run() {   
		                    T t = new T();   
		                    DateFormat df = (DateFormat) threadLocal.get();   
		                    if(df==null){   
		                        df = new SimpleDateFormat(DATE_FORMAT);   
		                        threadLocal.set(df);   
		                    } 
		                  // threadLocal.get();   
		                } 
	            });   
	            Thread t3 = new Thread(new Runnable() {   
	            	   public void run() {   
		                    T t = new T();   
		                    DateFormat df = (DateFormat) threadLocal.get();   
		                    if(df==null){   
		                        df = new SimpleDateFormat(DATE_FORMAT);   
		                        threadLocal.set(df);   
		                    } 
		                   //threadLocal.get();   
		                } 
	            });   
	            t1.start();   
	            t2.start();   
	            t3.start();   
	    }
}

说明每个线程的  threadLocal.get();   得到的都是null

每一次get的时候都调用getMap,返回一个map,判断map是否为null,如果为null,初始化;
因为每次获取的是线程自身的map,这个map第一次使用的时候肯定为null,调用初始化,输出了三次---------------,这么解释不知道您能否理解;
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}

26 楼 pouyang 2010-09-09  
不好意思刚代码好像有点问题,但是这样还是输出三次

import java.text.DateFormat;
import java.text.SimpleDateFormat;
public class T {
	 private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";   
	 private static ThreadLocal threadLocal = new ThreadLocal() {   
	        protected synchronized Object initialValue() {   
	        	System.out.println("-----");
	            return new SimpleDateFormat(DATE_FORMAT);   
	        }   
	    };   
	    public static void main(String[] args) {
	    	  Thread t1 = new Thread(new Runnable() {   
	                public void run() {   
	                    T t = new T();   
	                    DateFormat df = (DateFormat) threadLocal.get();   
	                    if(df==null){   
	                        df = new SimpleDateFormat(DATE_FORMAT);   
	                        threadLocal.set(df);   
	                    } 
	                   //threadLocal.get();   
	                }   
	            });   
	            Thread t2 = new Thread(new Runnable() {   
	            	   public void run() {   
		                    T t = new T();   
		                    DateFormat df = (DateFormat) threadLocal.get();   
		                    if(df==null){   
		                        df = new SimpleDateFormat(DATE_FORMAT);   
		                        threadLocal.set(df);   
		                    } 
		                  // threadLocal.get();   
		                } 
	            });   
	            Thread t3 = new Thread(new Runnable() {   
	            	   public void run() {   
		                    T t = new T();   
		                    DateFormat df = (DateFormat) threadLocal.get();   
		                    if(df==null){   
		                        df = new SimpleDateFormat(DATE_FORMAT);   
		                        threadLocal.set(df);   
		                    } 
		                   //threadLocal.get();   
		                } 
	            });   
	            t1.start();   
	            t2.start();   
	            t3.start();   
	    }
}

说明每个线程的  threadLocal.get();   得到的都是null
25 楼 niumd 2010-09-09  
pouyang 写道
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
public class T {
	 private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";   
	 private static ThreadLocal threadLocal = new ThreadLocal() {   
	        protected synchronized Object initialValue() {   
	        	System.out.println("-----");
	            return new SimpleDateFormat(DATE_FORMAT);   
	        }   
	    };   
	    public static void main(String[] args) {
	    	Thread t1 = new Thread(new Runnable() {
				public void run() {
					T t = new T();
					t.threadLocal.get();
				}
	    	});
	    	Thread t2 = new Thread(new Runnable() {
				public void run() {
					T t = new T();
					t.threadLocal.get();
				}
	    	});
	    	Thread t3 = new Thread(new Runnable() {
				public void run() {
					T t = new T();
					t.threadLocal.get();
				}
	    	});
	    	t1.start();
	    	t2.start();
	    	t3.start();
	    }
}

输出 三次,为什么多次实例化了new SimpleDateFormat(DATE_FORMAT);   ?
怎么以空间换时间了?

每个线程第一次获取都会初始化一次,你的实践验证能力很好,可能我开始描述的不是很清晰;
每一个线程初始化一次,每一个线程自身维护一个map,空间上占用内存较多,但不需要同步节约了时间;
24 楼 pouyang 2010-09-09  
import java.text.SimpleDateFormat;
public class T {
	 private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";   
	 private static ThreadLocal threadLocal = new ThreadLocal() {   
	        protected synchronized Object initialValue() {   
	        	System.out.println("-----");
	            return new SimpleDateFormat(DATE_FORMAT);   
	        }   
	    };   
	    public static void main(String[] args) {
	    	T tt1 = new T();
	    	T tt2 = new T();
	    	T tt3 = new T();
	    	tt1.threadLocal.get();
	    	tt2.threadLocal.get();
	    	tt3.threadLocal.get();
	    }
}

这样是输出了一次,在单线程里有效,与多线程无关吧?
23 楼 pouyang 2010-09-09  
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
public class T {
	 private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";   
	 private static ThreadLocal threadLocal = new ThreadLocal() {   
	        protected synchronized Object initialValue() {   
	        	System.out.println("-----");
	            return new SimpleDateFormat(DATE_FORMAT);   
	        }   
	    };   
	    public static void main(String[] args) {
	    	Thread t1 = new Thread(new Runnable() {
				public void run() {
					T t = new T();
					t.threadLocal.get();
				}
	    	});
	    	Thread t2 = new Thread(new Runnable() {
				public void run() {
					T t = new T();
					t.threadLocal.get();
				}
	    	});
	    	Thread t3 = new Thread(new Runnable() {
				public void run() {
					T t = new T();
					t.threadLocal.get();
				}
	    	});
	    	t1.start();
	    	t2.start();
	    	t3.start();
	    }
}

输出 三次,为什么多次实例化了new SimpleDateFormat(DATE_FORMAT);   ?
怎么以空间换时间了?
22 楼 kongxx 2010-09-08  
不错,典型的以空间换时间的实现,值得借鉴。
21 楼 笑我痴狂 2010-09-08  
是个好点子   threadLocal 这个类还是蛮有创意的  在hibernate中 的openSession的实现就是使用了threadLocal实现的 

重要的是思想  看怎么用
20 楼 pengzhoushuo 2010-09-08  
liujun999999 写道
pengzhoushuo 写道

liujun999999 写道
我觉得效率是否真的有提高?
每个线程第一次调用ThreadLocal的时候会调用initialValue,也就是说其实每个线程还是创建了一个SimpleDateFormat啊

呵呵,是static变量,不会每次都调用initialValue,初始化的时候一次而已。

查看ThreadLocal类的doc

引用


protected T initialValue()
返回此线程局部变量的当前线程的“初始值”。线程第一次使用 get() 方法访问变量时将调用此方法,但如果线程之前调用了 set(T) 方法,则不会对该线程再调用 initialValue 方法。通常,此方法对每个线程最多调用一次,但如果在调用 get() 后又调用了 remove(),则可能再次调用此方法。
该实现返回 null;如果程序员希望线程局部变量具有 null 以外的值,则必须为 ThreadLocal 创建子类,并重写此方法。通常将使用匿名内部类完成此操作。

返回:
返回此线程局部变量的初始值

并不是创建ThreadLocal对象的时候调用一次

sorry,确实是我看错了,应该是单个线程只new一次,每个线程还是会各new一次的
19 楼 archerfrank 2010-09-08  
pengjunwu 写道
不明白 如果format 有2种或者更多 怎么处理
String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; 
String DATE_FORMAT = "yyyy/MM/dd HH:mm:ss"; .....

每个一个thread local
18 楼 分离的北极熊 2010-09-08  
niumd 写道
分离的北极熊 写道
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只会在初始化时使用一次,加锁防止多线程不同步现象出现;


我知道在第一次的时候会return setInitialValue();
ThreadLocal在多线程的情况下,不也是确保每一个线程从ThreadLocal拿到的对象和其他线程从ThreadLocal拿到的对象不同么,而同一个线程下所有的ThreadLocal获取的对象是相同的。

还是不理解加的这个synchronized,源码中的initialValue() 也没有加任何的 同步啊

如果我们用ThreadLocal管理Hibernate的session 或者数据库连接Connection的时候,那都要类似的写法了

但是在换成普通写法的时候,缺没有看到同步synchronized的影子
 public static DateFormat getDateFormat()   
 {  
     DateFormat df = (DateFormat) threadLocal.get();  
     if(df==null){  
         df = new SimpleDateFormat(DATE_FORMAT)  
         threadLocal.set(df);  
     }  
     return df;  
 }  
17 楼 niumd 2010-09-08  
分离的北极熊 写道
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只会在初始化时使用一次,加锁防止多线程不同步现象出现;
16 楼 liujun999999 2010-09-08  
pengzhoushuo 写道

liujun999999 写道
我觉得效率是否真的有提高?
每个线程第一次调用ThreadLocal的时候会调用initialValue,也就是说其实每个线程还是创建了一个SimpleDateFormat啊

呵呵,是static变量,不会每次都调用initialValue,初始化的时候一次而已。

查看ThreadLocal类的doc

引用


protected T initialValue()
返回此线程局部变量的当前线程的“初始值”。线程第一次使用 get() 方法访问变量时将调用此方法,但如果线程之前调用了 set(T) 方法,则不会对该线程再调用 initialValue 方法。通常,此方法对每个线程最多调用一次,但如果在调用 get() 后又调用了 remove(),则可能再次调用此方法。
该实现返回 null;如果程序员希望线程局部变量具有 null 以外的值,则必须为 ThreadLocal 创建子类,并重写此方法。通常将使用匿名内部类完成此操作。

返回:
返回此线程局部变量的初始值

并不是创建ThreadLocal对象的时候调用一次
15 楼 liujun999999 2010-09-08  
分离的北极熊 写道
liujun999999 写道
我觉得效率是否真的有提高?
每个线程第一次调用ThreadLocal的时候会调用initialValue,也就是说其实每个线程还是创建了一个SimpleDateFormat啊


threadlocal.get()的时候会先从一个可以相当于缓存的ThreadLocalMap的类里面获取,如果没有才创建

这个我知道,也就是这个代码保证了一个线程最多只创建一个对象,也有一定的实际意义,学习
14 楼 pengzhoushuo 2010-09-08  
liujun999999 写道
我觉得效率是否真的有提高?
每个线程第一次调用ThreadLocal的时候会调用initialValue,也就是说其实每个线程还是创建了一个SimpleDateFormat啊

呵呵,是static变量,不会每次都调用initialValue,初始化的时候一次而已。
13 楼 分离的北极熊 2010-09-08  
liujun999999 写道
我觉得效率是否真的有提高?
每个线程第一次调用ThreadLocal的时候会调用initialValue,也就是说其实每个线程还是创建了一个SimpleDateFormat啊


threadlocal.get()的时候会先从一个可以相当于缓存的ThreadLocalMap的类里面获取,如果没有才创建
12 楼 archerfrank 2010-09-08  
最好再使用SoftReference,毕竟有的DateFormat一个线程用一次可能就在也不用了。
11 楼 liujun999999 2010-09-08  
我觉得效率是否真的有提高?
每个线程第一次调用ThreadLocal的时候会调用initialValue,也就是说其实每个线程还是创建了一个SimpleDateFormat啊

相关推荐

    java 简单的ThreadLocal示例

    在提供的"ThreadLocal示例"压缩包中,可能包含了一些具体的代码示例,展示如何在实际项目中运用ThreadLocal。通过查看这些示例,你可以更深入地理解ThreadLocal的工作方式以及如何在你的代码中有效地利用它。

    ThreadLocal应用示例及理解

    以上就是关于ThreadLocal的基本概念、使用方法、生命周期管理和实际应用示例的详细解释。了解并熟练掌握ThreadLocal可以帮助我们编写出更高效、更安全的多线程代码。在Java并发编程中,ThreadLocal是一个不可或缺的...

    ThreadLocal

    #### 五、Java API中的ThreadLocal示例 下面的示例展示了一个更具体的场景——为每个线程分配一个唯一的标识符。 ```java import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { // ...

    使用Java ThreadLocal.docx

    以下是一个完整的ThreadLocal示例,演示了如何在一个Runnable实例中使用ThreadLocal: ```java public class ThreadLocalExample { public static class MyRunnable implements Runnable { private ThreadLocal...

    Java多线程 之 临界区、ThreadLocal.docx

    下面是一个简单的ThreadLocal示例: ```java public class ThreadLocalTest { private static ThreadLocal&lt;Integer&gt; threadLocal = new ThreadLocal() { protected synchronized Integer initialValue() { ...

    彻底理解Java 中的ThreadLocal

    例如,以下是一个简单的ThreadLocal示例,用于生成线程安全的序列号: ```java private static ThreadLocal&lt;Integer&gt; seqNum = new ThreadLocal() { @Override protected Integer initialValue() { return 0; }...

    Android 中 ThreadLocal使用示例

    【Android中的ThreadLocal使用示例】 ThreadLocal是Java(因此也适用于Android)中的一种特殊变量类型,它允许每个线程拥有变量的独立副本。在多线程环境下,ThreadLocal可以帮助我们实现线程间的隔离,避免数据...

    正确理解ThreadLocal.pdf

    1. **数据库连接管理**:如上文的Hibernate示例,通过`ThreadLocal`管理每个线程的数据库连接,确保每个线程拥有独立的连接资源,避免了资源竞争。 2. **事务处理**:在事务管理中,`ThreadLocal`可以用于维护每个...

    ThreadLocal简单Demo

    下面是一个简单的`ThreadLocal`使用示例: ```java public class ThreadLocalDemo { public static void main(String[] args) { ThreadLocal&lt;String&gt; threadLocal = new ThreadLocal(); // 在主线程中设置值 ...

    事务的封装和Threadlocal实例

    下面是一个ThreadLocal的简单应用示例: ```java public class ConnectionHolder { private static final ThreadLocal&lt;Connection&gt; connectionHolder = new ThreadLocal(); public static void setConnection...

    Quartz-ThreadLocal.rar

    这个压缩包 "Quartz-ThreadLocal.rar" 内含的学习资源很可能是关于如何在 Quartz 调度器中结合使用 ThreadLocal 的示例。 Quartz 的核心功能包括: 1. **作业与触发器**:在 Quartz 中,任务被称为“作业”(Job)...

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

    深入理解ThreadLocal工作原理及使用示例 ThreadLocal是Java提供的一种解决多线程程序并发问题的工具类,自JDK1.2版本以来提供了java.lang.ThreadLocal类。ThreadLocal的主要作用是为每个使用该变量的线程提供独立的...

    Android 详解ThreadLocal及InheritableThreadLocal

    下面是一个简单的ThreadLocal使用示例: ```java public class MainActivity extends AppCompatActivity { private ThreadLocal&lt;Person&gt; mThreadLocal = new ThreadLocal(); private Person mPerson = new Person...

    理解threadlocal

    #### 四、ThreadLocal的简单实现示例 为了更直观地理解`ThreadLocal`的工作原理,我们可以创建一个简单的`SimpleThreadLocal`类。下面是一个基本的实现: ```java public class SimpleThreadLocal&lt;T&gt; { private ...

    threadlocal.rar

    通过上述分析,我们可以看出`ThreadLocal.rar`中的代码示例可能涉及了如何创建、设置、获取和清理ThreadLocal变量,以及如何在父子线程之间利用ThreadLocal进行通信。学习并理解ThreadLocal的原理和使用方法对于编写...

    ThreadLocal详解

    为了深入理解ThreadLocal的实现,我们可以构建一个简化的`SimpleThreadLocal`类示例,如代码清单1所示,其中包含了一个`Map`成员变量,用于存储线程及其对应的变量副本。 #### 结论 ThreadLocal作为一种独特的多...

    ThreadLocal,你真的了解吗?

    ThreadLocal 是 Java 中一个非常重要的工具类,它主要用于在多线程环境中为每个线程创建独立的、私有的变量副本,避免了线程之间数据共享带来的复杂性和安全性问题。了解 ThreadLocal,首先需要掌握 Java 中的四种...

    java的ThreadLocal[整理].pdf

    下面是一个简单的ThreadLocal使用示例: ```java public class ThreadLocalSample { public static void main(String[] args) { ThreadTest test1 = new ThreadTest(10); ThreadTest test2 = new ThreadTest(20)...

Global site tag (gtag.js) - Google Analytics