`
klyuan
  • 浏览: 182168 次
  • 性别: 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

    谈谈Java中的ThreadLocal

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

    线程安全你还在用synchronized?

    你还在用synchronized?线程安全相关知识深入剖析

    multiThread.jpg

    Java多线程技术思维导图,覆盖全面,可用作学习指导和查缺补漏,十分高效。...涵盖线程中断、线程状态、线程间通信,并发容器、ThreadLocal、Synchronized、CountDownLatch、CyclicBarrier等内容。

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

    synchronized关键字不属于方法特征签名的一部分,所以可以在覆盖方法的时候加上去。也就是说,在父类的方法声明上可以没有synchronized关键字,而在子类覆盖该方法时加上synchronized关键字。 注意:使用...

    Java并发编程学习笔记

    2、可重入锁与 Synchronized 的其他特性 3、ThreadLocal 的底层实现与使用 4、ReentrantLock底层实现和如何使用 5、Condition源码分析 6、ReentrantReadWriteLock底层实现原理 7、并发工具类CountDownLatch 、...

    Java中的线程同步与ThreadLocal无锁化线程封闭实现

    主要介绍了Java中的线程同步与ThreadLocal无锁化线程封闭实现,Synchronized关键字与ThreadLocal变量的使用是Java中线程控制的基础,需要的朋友可以参考下

    Java多线程编程中易混淆的3个关键字总结

    主要介绍了Java多线程编程中易混淆的3个关键字总结,本文总结了、volatile、ThreadLocal、synchronized等3个关键字,对这几个容易混淆概念的关键字分别做了讲解,需要的朋友可以参考下

    多线程相关代码(V3)

    多线程相关的(具体包括Lock synchronized Join ThreadLocal Executors CountDownLatch等)一些demo。

    java线程学习笔记

    2.3 线程本地存储(Java.lang.ThreadLocal) 15 2.4 线程阻塞 17 2.4.1 调用sleep(millisecond)使任务进入休眠状态 17 2.4.2 等待输出与输入 17 2.4.3 对象锁不可用 17 2.4.4 通过wait()使线程挂起。 17 2.5 线程...

    java学习整理文档.docx

    java 学习整理文档 Spring框架并...使用ThreadLocal ,在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(以空间换时间”),为每一个线程都提供了一份变量,因此可以同时访问而互不影响

    java并发编程面试题

    java并发编程 基础知识,守护线程与线程, 并行和并发有什么区别? 什么是上下文切换? 线程和进程区别 什么是线程和进程? 创建线程有哪几种方式?,如何避免线程死锁 ...ThreadLocal内存泄漏分析与

    Java并发编程原理与实战

    ThreadLocal 使用及实现原理.mp4 并发工具类CountDownLatch详解.mp4 并发工具类CyclicBarrier 详解.mp4 并发工具类Semaphore详解.mp4 并发工具类Exchanger详解.mp4 CountDownLatch,CyclicBarrier,Semaphore源码解析....

    Java并发编程相关技术使用案例

    线程创建、Synchronized和Reentrantlock锁的使用、线程安全问题演示、Condition的应用、CountDownLatch的应用、Cyclicbarrier的应用、Semaphore的应用、线程池的应用、Completablefuture的应用、手写阻塞队列、fork...

    java多线程安全性基础介绍.pptx

    threadLocal 为每个线程保存一个副本 类似于一个以线程id为key的map 不可变对象状态 final 关键共享资源上互斥,变并发为串行即同步 锁 分类 显示锁 Lock Lock是个接口 实现类 ReentrantLock 可重入锁...

    【2018最新最详细】并发多线程教程

    4.彻底理解synchronized 5.彻底理解volatile 6.你以为你真的了解final吗? 7.三大性质总结:原子性、可见性以及有序性 8.初识Lock与AbstractQueuedSynchronizer(AQS) 9.深入理解AbstractQueuedSynchronizer(AQS) 10....

    Java进阶教程,面试大全,包罗万象

    synchronized在静态方法和普通方法的区别。 怎么实现所有线程在等待某个事件的发生才会去执行。 CAS。 Hashtable是怎么加锁的。 HashMap的并发问题。 ConcurrenHashMap 介绍。 AQS。 如何检测死锁,怎么预防死锁。 ...

    Java进阶教程,面试大全

    synchronized在静态方法和普通方法的区别。 怎么实现所有线程在等待某个事件的发生才会去执行。 CAS。 Hashtable是怎么加锁的。 HashMap的并发问题。 ConcurrenHashMap 介绍。 AQS。 如何检测死锁,怎么预防死锁。 ...

    java线程安图分析(含测试代码)

    目录: 基础概念 造成线程不安全的条件 变量种类与线程安全 如何避免线程不安全 synchronized关键字使用和原理 jdk多线程并发包 THREADLOCAL 测试工程

    BATJ面试题汇总及详解65页

    BATJ面试题汇总及详解65页 JAVA面试题汇总 ...volatile、ThreadLocal的使用场景和原理? ThreadLocal什么时候会出现OOM的情况?为什么? synchronized、volatile区别? synchronized锁粒度、模拟死锁场景?

Global site tag (gtag.js) - Google Analytics