`
klyuan
  • 浏览: 184586 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

ThreadLocal与synchronized

    博客分类:
  • java
阅读更多
ThreadLocal与synchronized
Java良好的支持多线程。使用java,我们可以很轻松的编程一个多线程程序。但是使用多线程可能会引起并发访问的问题。synchronized和ThreadLocal都是用来解决多线程并发访问的问题。大家可能对synchronized较为熟悉,而对ThreadLocal就要陌生得多了。
并发问题。当一个对象被两个线程同时访问时,可能有一个线程会得到不可预期的结果。

一个简单的java类Studnet
public class Student {
  private int age=0;
  
  public int getAge() {
	  return this.age;
	  
  }
  
  public void setAge(int age) {
	  this.age = age;
  }
}

一个多线程类ThreadDemo.
这个类有一个Student的私有变量,在run方法中,它随机产生一个整数。然后设置到student变量中,从student中读取设置后的值。然后睡眠5秒钟,最后再次读student的age值。

public class ThreadDemo implements Runnable{
  Student student = new Student();
  public static void main(String[] agrs) {
	 ThreadDemo td = new ThreadDemo();
	 Thread t1 = new Thread(td,"a");
	 Thread t2 = new Thread(td,"b");
    t1.start();
    t2.start();

  }
/* (non-Javadoc)
 * @see java.lang.Runnable#run()
 */
 public void run() {
	 accessStudent();
 }
 
 public void accessStudent() {
	    String currentThreadName = Thread.currentThread().getName();
	    System.out.println(currentThreadName+" is running!");
	   // System.out.println("first  read age is:"+this.student.getAge());
	    Random random = new Random();
	    int age = random.nextInt(100);
	    System.out.println("thread "+currentThreadName +" set age to:"+age);
	   
	    this.student.setAge(age);
	    System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());
	    try {
	    Thread.sleep(5000);
	    }
	    catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
	    System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
	     
 }
  
}
运行这个程序,屏幕输出如下:
a is running!
b is running!
thread b set age to:33
thread b first  read age is:33
thread a set age to:81
thread a first  read age is:81
thread b second read age is:81
thread a second read age is:81

需要注意的是,线程a在同一个方法中,第一次读取student的age值与第二次读取值不一致。这就是出现了并发问题。

synchronized
上面的例子,我们模似了一个并发问题。Java提供了同步机制来解决并发问题。synchonzied关键字可以用来同步变量,方法,甚至同步一个代码块。
使用了同步后,一个线程正在访问同步对象时,另外一个线程必须等待。
  Synchronized同步方法
现在我们可以对accessStudent方法实施同步。
public synchronized void  accessStudent()
再次运行程序,屏幕输出如下:
a is running!
thread a set age to:49
thread a first  read age is:49
thread a second read age is:49
b is running!
thread b set age to:17
thread b first  read age is:17
thread b second read age is:17

加上了同步后,线程b必须等待线程a执行完毕后,线程b才开始执行。

对方法进行同步的代价是非常昂贵的。特别是当被同步的方法执行一个冗长的操作。这个方法执行会花费很长的时间,对这样的方法进行同步可能会使系统性能成数量级的下降。

Synchronized同步块
  在accessStudent方法中,我们真实需要保护的是student变量,所以我们可以进行一个更细粒度的加锁。我们仅仅对student相关的代码块进行同步。
	    synchronized(this) {
	    Random random = new Random();
	    int age = random.nextInt(100);
	    System.out.println("thread "+currentThreadName +" set age to:"+age);
	   
	    this.student.setAge(age);
	   
	    System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());
	    try {
	    Thread.sleep(5000);
	    }
	    catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
	    }
运行方法后,屏幕输出:
a is running!
thread a set age to:18
thread a first  read age is:18
b is running!
thread a second read age is:18
thread b set age to:62
thread b first  read age is:62
thread b second read age is:62

需要特别注意这个输出结果。
这个执行过程比上面的方法同步要快得多了。
只有对student进行访问的代码是同步的,而其它与部份代码却是异步的了。而student的值并没有被错误的修改。如果是在一个真实的系统中,accessStudent方法的操作又比较耗时的情况下。使用同步的速度几乎与没有同步一样快。

使用同步锁
稍微把上面的例子改一下,在ThreadDemo中有一个私有变量count,。
   private int count=0;
在accessStudent()中, 线程每访问一次,count都自加一次, 用来记数线程访问的次数。
	    try {
	    this.count++;
	    Thread.sleep(5000);
	    }catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
  为了模拟线程,所以让它每次自加后都睡眠5秒。
accessStuden()方法的完整代码如下:
   	    String currentThreadName = Thread.currentThread().getName();
	    System.out.println(currentThreadName+" is running!");
			    try {
	    this.count++;
	    Thread.sleep(5000);
	    }catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
		    System.out.println("thread "+currentThreadName+" read count:"+this.count);
	    
	   
	    synchronized(this) {
	    Random random = new Random();
	    int age = random.nextInt(100);
	    System.out.println("thread "+currentThreadName +" set age to:"+age);
	   
	    this.student.setAge(age);
	   
	    System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());
	    try {
	    Thread.sleep(5000);
	    }
	    catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
	    }
	    System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
    运行程序后,屏幕输出:
a is running!
b is running!
thread a read count:2
thread a set age to:49
thread a first  read age is:49
thread b read count:2
thread a second read age is:49
thread b set age to:7
thread b first  read age is:7
thread b second read age is:7

我们仍然对student对象以synchronized(this)操作进行同步。
我们需要在两个线程中共享count失败。

所以仍然需要对count的访问进行同步操作。
		 synchronized(this) {
	    try {
	    this.count++;
	    Thread.sleep(5000);
	    }catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
	    }
	    System.out.println("thread "+currentThreadName+" read count:"+this.count);
	    
	   
	    synchronized(this) {
	    Random random = new Random();
	    int age = random.nextInt(100);
	    System.out.println("thread "+currentThreadName +" set age to:"+age);
	   
	    this.student.setAge(age);
	   
	    System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());
	    try {
	    Thread.sleep(5000);
	    }
	    catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
	    }
	    System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
	    long endTime = System.currentTimeMillis();
	    long spendTime = endTime - startTime;
	    System.out.println("花费时间:"+spendTime +"毫秒");

程序运行后,屏幕输出
a is running!
b is running!
thread a read count:1
thread a set age to:97
thread a first  read age is:97
thread a second read age is:97
花费时间:10015毫秒
thread b read count:2
thread b set age to:47
thread b first  read age is:47
thread b second read age is:47
花费时间:20124毫秒

我们在同一个方法中,多次使用synchronized(this)进行加锁。有可能会导致太多额外的等待。
应该使用不同的对象锁进行同步。

设置两个锁对象,分别用于student和count的访问加锁。
 private Object studentLock = new Object();
private Object countLock = new Object();

accessStudent()方法如下:
	 long startTime = System.currentTimeMillis();
	    String currentThreadName = Thread.currentThread().getName();
	    System.out.println(currentThreadName+" is running!");
	   // System.out.println("first  read age is:"+this.student.getAge());

		 synchronized(countLock) {
	    try {
	    this.count++;
	    Thread.sleep(5000);
	    }catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
	    }
	    System.out.println("thread "+currentThreadName+" read count:"+this.count);
	    
	   
	    synchronized(studentLock) {
	    Random random = new Random();
	    int age = random.nextInt(100);
	    System.out.println("thread "+currentThreadName +" set age to:"+age);
	   
	    this.student.setAge(age);
	   
	    System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());
	    try {
	    Thread.sleep(5000);
	    }
	    catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
	    }
	    System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
	    long endTime = System.currentTimeMillis();
	    long spendTime = endTime - startTime;
	    System.out.println("花费时间:"+spendTime +"毫秒");

这样对count和student加上了两把不同的锁。

运行程序后,屏幕输出:
a is running!
b is running!
thread a read count:1
thread a set age to:48
thread a first  read age is:48
thread a second read age is:48
花费时间:10016毫秒
thread b read count:2
thread b set age to:68
thread b first  read age is:68
thread b second read age is:68
花费时间:20046毫秒
与两次使用synchronized(this)相比,使用不同的对象锁,在性能上可以得到更大的提升。

由此可见synchronized是实现java的同步机制。同步机制是为了实现同步多线程对相同资源的并发访问控制。保证多线程之间的通信。
可见,同步的主要目的是保证多线程间的数据共享。同步会带来巨大的性能开销,所以同步操作应该是细粒度的。如果同步使用得当,带来的性能开销是微不足道的。使用同步真正的风险是复杂性和可能破坏资源安全,而不是性能。


ThreadLocal
由上面可以知道,使用同步是非常复杂的。并且同步会带来性能的降低。Java提供了另外的一种方式,通过ThreadLocal可以很容易的编写多线程程序。从字面上理解,很容易会把ThreadLocal误解为一个线程的本地变量。其它ThreadLocal并不是代表当前线程,ThreadLocal其实是采用哈希表的方式来为每个线程都提供一个变量的副本。从而保证各个线程间数据安全。每个线程的数据不会被另外线程访问和破坏。

我们把第一个例子用ThreadLocal来实现,但是我们需要些许改变。
Student并不是一个私有变量了,而是需要封装在一个ThreadLocal对象中去。调用ThreadLocal的set方法,ThreadLocal会为每一个线程都保持一份Student变量的副本。所以对student的读取操作都是通过ThreadLocal来进行的。
	protected Student getStudent() {
		Student student = (Student)studentLocal.get();
		if(student == null) {
			student = new Student();
			studentLocal.set(student);
		}
		return student;
	}
	
	protected void setStudent(Student student) {
		studentLocal.set(student);
	}

accessStudent()方法需要做一些改变。通过调用getStudent()方法来获得当前线程的Student变量,如果当前线程不存在一个Student变量,getStudent方法会创建一个新的Student变量,并设置在当前线程中。
    Student student = getStudent();
    student.setAge(age);
accessStudent()方法中无需要任何同步代码。

完整的代码清单如下:
TreadLocalDemo.java
public class TreadLocalDemo implements Runnable {
   private final static  ThreadLocal studentLocal = new ThreadLocal();
   
   public static void main(String[] agrs) {
	   TreadLocalDemo td = new TreadLocalDemo();
		 Thread t1 = new Thread(td,"a");
		 Thread t2 = new Thread(td,"b");
		
	    t1.start();
	    t2.start();
	   
	   


	  }
   
	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		 accessStudent();
	}

	public  void  accessStudent() {
		
	    String currentThreadName = Thread.currentThread().getName();
	    System.out.println(currentThreadName+" is running!");
	    Random random = new Random();
	    int age = random.nextInt(100);
	    System.out.println("thread "+currentThreadName +" set age to:"+age);
	    Student student = getStudent();
	    student.setAge(age);
	    System.out.println("thread "+currentThreadName+" first  read age is:"+student.getAge());
	    try {
	    Thread.sleep(5000);
	    }
	    catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
	    System.out.println("thread "+currentThreadName +" second read age is:"+student.getAge());
	    
	}
	
	protected Student getStudent() {
		Student student = (Student)studentLocal.get();
		if(student == null) {
			student = new Student();
			studentLocal.set(student);
		}
		return student;
	}
	
	protected void setStudent(Student student) {
		studentLocal.set(student);
	}
}
运行程序后,屏幕输出:
b is running!
thread b set age to:0
thread b first  read age is:0
a is running!
thread a set age to:17
thread a first  read age is:17
thread b second read age is:0
thread a second read age is:17

可见,使用ThreadLocal后,我们不需要任何同步代码,却能够保证我们线程间数据的安全。
而且,ThreadLocal的使用也非常的简单。
我们仅仅需要使用它提供的两个方法
void set(Object obj) 设置当前线程的变量的副本的值。
Object get() 返回当前线程的变量副本

另外ThreadLocal还有一个protected的initialValue()方法。返回变量副本在当前线程的初始值。默认为null

ThreadLocal是怎么做到为每个线程都维护一个变量的副本的呢?
我们可以猜测到ThreadLocal的一个简单实现
public class ThreadLocal
{
 private Map values = Collections.synchronizedMap(new HashMap());
 public Object get()
 {
  Thread curThread = Thread.currentThread(); 
  Object o = values.get(curThread); 
  if (o == null && !values.containsKey(curThread))
  {
   o = initialValue();
   values.put(curThread, o); 
  }
  return o; 
 }

 public void set(Object newValue)
 {
  values.put(Thread.currentThread(), newValue);
 }

 public Object initialValue()
 {
  return null; 
 }
}

由此可见,ThreadLocal通过一个Map来为每个线程都持有一个变量副本。这个map以当前线程为key。与synchronized相比,ThreadLocal是以空间换时间的策略来实现多线程程序。

Synchronized还是ThreadLocal?
ThreadLocal以空间换取时间,提供了一种非常简便的多线程实现方式。因为多个线程并发访问无需进行等待,所以使用ThreadLocal会获得更大的性能。虽然使用ThreadLocal会带来更多的内存开销,但这点开销是微不足道的。因为保存在ThreadLocal中的对象,通常都是比较小的对象。另外使用ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。
分享到:
评论
91 楼 开心^o^就好 2008-05-22  
62318889   群研究struts2、spring、hibernate 及一些开源框架,有兴趣的朋友欢迎加入
90 楼 elice 2008-05-22  
signal!
89 楼 andy54321 2008-04-06  
好帖,不过太长,没看完,
而且现在也没有足够研究过相关thead的东西,
收藏,有需要了一定好好学习研究
88 楼 bruce.lu 2008-04-06  
klyuan 写道
ThreadLocal与synchronized
Synchronized同步块
  在accessStudent方法中,我们真实需要保护的是student变量,所以我们可以进行一个更细粒度的加锁。我们仅仅对student相关的代码块进行同步。

 synchronized(this) {
	    Random random = new Random();
	    int age = random.nextInt(100);
	    System.out.println("thread "+currentThreadName +" set age to:"+age);
	   
	    this.student.setAge(age);
	   
	    System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());
	    try {
	    Thread.sleep(5000);
	    }
	    catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
}

刚路过看到, 仔细看了一下, 补充一点: 其实这段代码并没有真正的同步你的输出。 the second read 那段的输出代码也应该同步, 这样才同步了你的功能块。 将sleep的时间设成500就可以看出来了。

System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge()); 

放到同步块里面catch之后。
87 楼 wswz 2008-04-02  
看完了,好文!谢谢分享
86 楼 jieyuan_cg 2008-04-02  
http://www.iteye.com/post/504793
这篇帖子非常正确地解释了ThreadLocal。。。非常到位!
85 楼 jieyuan_cg 2008-04-01  
是不是可以这样理解,ThreadLocal的作用是为了不采用全局变量来解决也可以采用全局变量来解决的问题呢??

不知道是不是这样。。。
84 楼 realorg 2007-10-29  
前一个回帖被评为灌水回复贴。

现在,偶也聊两句非灌水的:
ThreadLocal类 通过构造共享变量的线程副本,使得需要多个线程访问的变量不再“共享”,而是使用自己线程中的此“副本”。通过这种方式,可以使“竞争条件”失效,即多个线程访问变量不再产生竞争,不会产生对数据“脏读”、“读过期数据”等等问题。

synchronized关键字 通过将多线程访问的共享变量与线程锁相关而实现对共享变量的互斥访问。每个线程在进入synchronized块时,都会获取一个与同步块共享对象相关的互斥锁,而退出synchronized块(其中的所有语句都已执行完毕)时会释放锁,如果线程在执行 synchronized 块时,由于时间片到而被调度到等待状态,则其他线程由于无法得到该互斥锁而无法进入synchronized块,会等待。之前被中断的线程在时间片到时,再次进入synchronized块而且接着以前的任务继续执行,因为synchronized块是“可重入”的。

也就是说:
synchronized块将多线程带来的不确定的无序状态变成“有序”的,因为各个线程在访问 synchronized 块时被强制排队,因而也就保证了 synchronized 块中的所有操作具有“原子性”,不会有“竞争条件”存在。由于强制排队,自然会带来效率问题。通常情况下,将整个方法指定为 synchronized 做法是不合时宜的,除非必须这么作。

ThreadLocal类 是把多线程共享的变量“私有化”,只是各自(每个线程)都保存了自己的副本,对此 副本的读写操作不会影响到其他线程。也就不需要各线程之间的协调了。



83 楼 crazyox 2007-10-27  
晕哦,好不容易看完了,没想到大家这样评论,也有不少人在争论,既然觉得不对是否有人能完整的更正一下或者重新发表一篇呢?谢谢啦!
82 楼 williamy 2007-10-15  
有2个小宝宝,要抢喝奶,于是“同步”是这样来实现的,给宝宝一个先后顺序,一个一个的喝,ThreadLocal是另外一种方式,就是有几个宝宝就几个奶妈,于是一个宝宝配一个奶妈,特别是杏仁茶都磨得出的那种奶妈,这两种方式都能让宝宝不闹,
81 楼 realorg 2007-10-12  
楼主,你的文章怎么那么长啊,怎么那么好看啊?

楼主,你的头像图片中的美女叫什么名字,可否介绍与我?
80 楼 laosu 2007-08-06  
看了一天的ThreadLocal,看到lz这篇才感觉对ThreadLocal有了一个深入的理解,通过两个比较,能更深入的了解ThreadLocal。看这篇文章之前也看其他的介绍ThreadLocal的文章,基本看不懂,一头雾水。谢谢LZ!
79 楼 lindongxiao 2007-07-29  
貌似threadloacl跟线程安全没什么关系,理解保证得到的对象是单例
网上的一个简单实现public class ThreadLocal {
    private Map values = Collections.synchronizedMap(new HashMap());
    public Object get() {
        Thread curThread = Thread.currentThread();
        Object o = values.get(curThread);
        if (o == null && !values.containsKey(curThread)) {
            o = initialValue();
            values.put(curThread, o);
        }
        return o;
    }
    public void set(Object newValue) {
        values.put(Thread.currentThread(), newValue);
    }
    public Object initialValue() {
        return null;
    }
}
78 楼 baallee 2007-07-06  
我想现在处理并发的方式已经简单很多了.
JDK5提供了一组concurrent的对象给我们访问,这可是大师Doug Lea的作品
jdk6更加完善了这一package.
大家可以看下java.util.concurrent
77 楼 klyuan 2007-07-06  
常人都喜欢纵向的钻研问题!
而不善于横向的分析,比较问题!
所以就被有人投诉了!
当然,本文的某些地方的确不够严谨!
76 楼 inspnovo 2007-07-06  
我认为ThreadLocal是在多线程环境下,为各线程的资源(存放在threadLocals中)提供了一个管理方法。
目的并不是为了解决多线程环境中的并发访问控制。
75 楼 answer 2007-06-28  
俺觉得.楼主的主要意思就是如何避免线程之间的冲突,说明了两种情况而已
74 楼 julyboxer 2007-05-31  
嗯。。ThreadLocal根本不能解决共享资源访问的问题,我想有很多人误用了synchronized,原本不需要用到同步,用ThreadLocal就能解决问题了.却用同步这种极其降低性能的方法。。所以能够ThreadLocal来代替Synchronized的地方,最好用ThreadLocal,真正只能用Synchronized的地方,ThreadLocal也只能干蹬眼
73 楼 anweixiao 2007-05-31  
因为引用的东西比较长,同时又不能在代码块中“着色”来强调我需要突出的问题,从我上面的回复不知道这里的各位是否明白了我的意图:其实LZ在解释synchornized和ThreadLocal的过程中改变了
accessStudent()中的student,正是这一点的改变,才使得楼主感觉到了ThreadLocal可以做到同步…………,如果是这种情况下也能同步的话,我想楼主是对的,但根据楼主的逻辑,下面的处理方法恐怕测试通不过:
public class ThreadDemo implements Runnable {
	private ThreadLocal local = new ThreadLocal();
	private Student student = getStudent();
           …………………………
            …………………………………

    public void  accessStudent(){
		//synchronized(this){	
        String currentThreadName = Thread.currentThread().getName();   
        System.out.println(currentThreadName+" is running!");   
       // System.out.println("first  read age is:"+this.student.getAge());   
        Random random = new Random();   
        int age = random.nextInt(100);   
        System.out.println("thread "+currentThreadName +" set age to:"+age);   
        //Student student =  new Student();
        student.setAge(age);   
        System.out.println("thread "+currentThreadName+
        		" first  read age is:"+student.getAge());   
        try {   
        Thread.sleep(5000);   
        } 

上面的代码中accessStudent使用的实例student来自ThreadDemo,这也是和楼主使用synchronized的时候是相同“地位”的,
72 楼 anweixiao 2007-05-31  
klyuan 写道
ThreadLocal与synchronized
这个类有一个Student的私有变量,在run方法中,它随机产生一个整数。然后设置到student变量中,从student中读取设置后的值。然后睡眠5秒钟,最后再次读student的age值。

public class ThreadDemo implements Runnable{
  [color=red]Student student = new Student();[/color]
  public static void main(String[] agrs) {
	 ThreadDemo td = new ThreadDemo();
	 Thread t1 = new Thread(td,"a");
	 Thread t2 = new Thread(td,"b");
    t1.start();
    t2.start();

  }
/* (non-Javadoc)
 * @see java.lang.Runnable#run()
 */
 public void run() {
	 accessStudent();
 }
 
 public void accessStudent() {
	    String currentThreadName = Thread.currentThread().getName();
	    System.out.println(currentThreadName+" is running!");
	   // System.out.println("first  read age is:"+this.student.getAge());
	    Random random = new Random();
	    int age = random.nextInt(100);
	    System.out.println("thread "+currentThreadName +" set age to:"+age);
	   
	   this.student.setAge(age);
	    System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());
	    try {
	    Thread.sleep(5000);
	    }
	    catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
	    System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());
	     
 } 
}

注意:这里的this.student.setAge(age);

我们把第一个例子用ThreadLocal来实现,但是我们需要些许改变。

Student并不是一个私有变量了,而是需要封装在一个ThreadLocal对象中去。调用ThreadLocal的set方法,ThreadLocal会为每一个线程都保持一份Student变量的副本。所以对student的读取操作都是通过ThreadLocal来进行的。
	protected Student getStudent() {
		Student student = (Student)studentLocal.get();
		if(student == null) {
			student = new Student();
			studentLocal.set(student);
		}
		return student;
	}
	
	protected void setStudent(Student student) {
		studentLocal.set(student);
	}

accessStudent()方法需要做一些改变。通过调用getStudent()方法来获得当前线程的Student变量,如果当前线程不存在一个Student变量,getStudent方法会创建一个新的Student变量,并设置在当前线程中。
    Student student = getStudent();
    student.setAge(age);
accessStudent()方法中无需要任何同步代码。

完整的代码清单如下:
TreadLocalDemo.java
public class TreadLocalDemo implements Runnable {
   private final static  ThreadLocal studentLocal = new ThreadLocal();
   
   public static void main(String[] agrs) {
	   TreadLocalDemo td = new TreadLocalDemo();
		 Thread t1 = new Thread(td,"a");
		 Thread t2 = new Thread(td,"b");
		
	    t1.start();
	    t2.start();
	   
	   


	  }
   
	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		 accessStudent();
	}

	public  void  accessStudent() {
		
	    String currentThreadName = Thread.currentThread().getName();
	    System.out.println(currentThreadName+" is running!");
	    Random random = new Random();
	    int age = random.nextInt(100);
	    System.out.println("thread "+currentThreadName +" set age to:"+age);
	    Student student = getStudent();
	    student.setAge(age);
	    System.out.println("thread "+currentThreadName+" first  read age is:"+student.getAge());
	    try {
	    Thread.sleep(5000);
	    }
	    catch(InterruptedException ex) {
	    	ex.printStackTrace();
	    }
	    System.out.println("thread "+currentThreadName +" second read age is:"+student.getAge());
	    
	}
	
	protected Student getStudent() {
		Student student = (Student)studentLocal.get();
		if(student == null) {
			student = new Student();
			studentLocal.set(student);
		}
		return student;
	}
	
	protected void setStudent(Student student) {
		studentLocal.set(student);
	}
}

Student student = getStudent();
student.setAge(age);


可见,使用ThreadLocal后,我们不需要任何同步代码,却能够保证我们线程间数据的安全。
而且,ThreadLocal的使用也非常的简单。
我们仅仅需要使用它提供的两个方法
void set(Object obj) 设置当前线程的变量的副本的值。
Object get() 返回当前线程的变量副本

由此可见,ThreadLocal通过一个Map来为每个线程都持有一个变量副本。这个map以当前线程为key。与synchronized相比,ThreadLocal是以空间换时间的策略来实现多线程程序。

Synchronized还是ThreadLocal?
ThreadLocal以空间换取时间,提供了一种非常简便的多线程实现方式。因为多个线程并发访问无需进行等……………………


注意引用中标记了红色的地方,也就是使用ThreadLocal改造前后Student student的作用范围的变化,如果使用这中方式来解决并发的问题,我想也完全没有必要引入ThreadLocal了,直接在accessStudent方法中这样处理就OK了 Student student =  new Student();:
    	public void  accessStudent(){
		//synchronized(this){	
		String currentThreadName = Thread.currentThread().getName();   
        System.out.println(currentThreadName+" is running!");   
       // System.out.println("first  read age is:"+this.student.getAge());   
        Random random = new Random();   
        int age = random.nextInt(100);   
        System.out.println("thread "+currentThreadName +" set age to:"+age);   
        Student student =  new Student();
        student.setAge(age);


相信这样每个线程,读出来的数据也不可能会有交叉的问题.

相关推荐

    Synchronized与ThreadLocal

    ### Synchronized与ThreadLocal #### 一、Synchronized机制详解 **Synchronized** 是 Java 中一个非常重要的关键字,主要用于实现线程同步。它通过在对象上加锁来确保多个线程能够安全地访问共享资源。 - **作用...

    ThreadLocal

    ThreadLocal通常被用来解决线程共享数据时可能出现的并发问题,避免了使用synchronized关键字进行同步控制的复杂性。 在Java中,ThreadLocal的工作原理是为每个线程创建一个单独的存储空间,每个线程可以独立地读写...

    Java中ThreadLocal的设计与使用

    ThreadLocal的生命周期与创建它的线程紧密相关。一旦线程结束,其ThreadLocal变量及其存储的值也会自动被清理。但是,如果线程持续存在且不调用`remove()`,ThreadLocal变量可能会导致内存泄漏,因为它们占用的内存...

    谈谈Java中的ThreadLocal

     ThreadLocal一般称为线程本地变量,它是一种特殊的线程绑定机制,将变量与线程绑定在一起,为每一个线程维护一个独立的变量副本。通过ThreadLocal可以将对象的可见范围限制在同一个线程内。  跳出误区  需要...

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

    总结来说,Java中的临界区通过`synchronized`关键字或Lock对象来保证多线程环境下的数据一致性,而ThreadLocal则通过为每个线程提供变量的独立副本,避免了共享资源的并发问题。在实际编程中,开发者需要根据具体...

    ThreadLocal源码分析和使用

    private Map valueMap=Collections.synchronizedMap(new HashMap()); public void set(Object newValue){ valueMap.put(Thread.currentThread(),newValue);//键为线程对象,值为本线程的变量副本 } public ...

    8个案例详解教会你ThreadLocal.docx

    - 在面试中,可能会遇到关于 `ThreadLocal` 生命周期管理、内存泄漏、与 `synchronized` 的比较以及在实际应用中的场景分析等问题。 通过以上介绍,我们可以了解到 `ThreadLocal` 在处理多线程环境中提供了独特的...

    ThreadLocal详解

    与传统的使用`synchronized`关键字或`Lock`接口来实现线程同步不同,ThreadLocal提供了另一种解决线程安全问题的思路——即为每个线程创建独立的变量副本,避免了线程间的变量共享所带来的同步问题,从而提高了程序...

    2、导致JVM内存泄露的ThreadLocal详解

    `ThreadLocal`类内部维护了一个`ThreadLocalMap`结构,该结构存储了线程与线程局部变量之间的映射关系。每当一个新的线程创建并首次访问某个`ThreadLocal`实例时,都会在该线程的`ThreadLocalMap`中添加一个新的键值...

    彻底理解ThreadLocal 1

    传统的线程同步,如使用`synchronized`关键字,会使得多个线程在访问共享资源时需要排队执行,确保了线程安全,但牺牲了并发性能。而ThreadLocal则通过为每个线程创建单独的变量副本,消除了并发冲突,提升了程序的...

    深入理解 Java 之 ThreadLocal 工作原理1

    当我们创建一个ThreadLocal实例并调用其set方法时,实际上是将值与当前线程关联起来。每个线程都有一个ThreadLocalMap,这是ThreadLocal内部的一个静态内部类,用来存储ThreadLocal对象和它们对应的值。 在...

    java面试题

    Java面试中,ThreadLocal和Synchronized是经常被讨论的话题,它们是Java并发编程中的关键概念。ThreadLocal,顾名思义,线程局部变量,它为每个线程提供了一个独立的变量副本,使得每个线程都可以独立地改变自己的...

    Java非线程安全类变线程安全类.pdf

    Java 非线程安全类变线程安全类 Java 中的非线程安全类是指有状态的类,即有属性的类,这些类在多线程...Java 中的非线程安全类可以通过使用 ThreadLocal 或 synchronized 关键字来实现线程安全,避免线程安全问题。

    18 线程作用域内共享变量—深入解析ThreadLocal.pdf

    传统的解决方案包括使用`Atomic`类、`volatile`关键字以及`synchronized`关键字来保证多线程环境下的数据一致性。然而,这些同步机制并不总是最优解,特别是在需要线程内共享变量且避免线程间干扰的情况下。此时,`...

    java ThreadLocal使用案例详解

    Java ThreadLocal使用案例详解 Java ThreadLocal是Java语言中的一种机制,用于为每个线程提供一个独立的变量副本,以解决多线程环境下共享变量的线程安全问题。在本文中,我们将详细介绍Java ThreadLocal的使用案例...

    java 中ThreadLocal 的正确用法

    在 Java 中,ThreadLocal 实例通常是 private static 字段,位于希望与线程关联状态的类中。例如,在 SerialNum 类中,我们定义了一个私有静态的 ThreadLocal 实例(serialNum),用于维护每个线程的序列号。 ```...

Global site tag (gtag.js) - Google Analytics