Thespringofmulti-thread——线程安全的另一种解决思路_第1页
Thespringofmulti-thread——线程安全的另一种解决思路_第2页
Thespringofmulti-thread——线程安全的另一种解决思路_第3页
Thespringofmulti-thread——线程安全的另一种解决思路_第4页
Thespringofmulti-thread——线程安全的另一种解决思路_第5页
已阅读5页,还剩22页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、The spring of multi-thread The spring of multi-thread 线程安线程安全的另一种解决思路全的另一种解决思路我需要一个可靠的单例模式!上堂回顾public class InnDao private volatile static InnDao instance;private InnDao()public static InnDao getInstance() if (instance = null)synchronized (InnDao.class) / 1if (instance = null) / 2 instance = new Inn

2、Dao(); / 3return instance;public final Model.Finder finder = new Model.Finder(Integer.class, Inn.class);关于关于VolatileVolatile与同步操作共同使用的思考与同步操作共同使用的思考public class ThreadClient extends Thread private static int count; public void run() for(int i=0;i3;i+) System.out.println(当前线程名:+Thread.currentThread()

3、.getName() + count=+count); count+; public static void main(String args) ThreadClient tc1 = new ThreadClient(); ThreadClient tc2 = new ThreadClient(); ThreadClient tc3 = new ThreadClient(); tc1.start(); tc2.start(); tc3.start(); 当前线程名:当前线程名:Thread-1 count=0当前线程名:当前线程名:Thread-1 count=1当前线程名:当前线程名:Thr

4、ead-1 count=2当前线程名:当前线程名:Thread-2 count=3当前线程名:当前线程名:Thread-2 count=4当前线程名:当前线程名:Thread-2 count=5当前线程名:当前线程名:Thread-0 count=6当前线程名:当前线程名:Thread-0 count=7当前线程名:当前线程名:Thread-0 count=8public class ThreadClient extends Thread private static ThreadLocal count = new ThreadLocal(); public ThreadClient(Inte

5、ger value) count.set(value); public void run() for(int i=0;i3;i+) System.out.println(当前线程名:+Thread.currentThread().getName() + count=+count.get(); count.set(count.get()+1); try TimeUnit.SECONDS.sleep(1); catch (InterruptedException e) e.printStackTrace(); public static void main(String args) ThreadC

6、lient tc1 = new ThreadClient(0); ThreadClient tc2 = new ThreadClient(0); ThreadClient tc3 = new ThreadClient(0); tc1.start(); tc2.start(); tc3.start(); Exception in thread Thread-1 java.lang.NullPointerExceptionat com.fanqie.oms.thread.ThreadLocal.ThreadClient.run(ThreadClient.java:21)Exception in t

7、hread Thread-0 java.lang.NullPointerExceptionat com.fanqie.oms.thread.ThreadLocal.ThreadClient.run(ThreadClient.java:21)当前线程名:Thread-1 count=null当前线程名:Thread-0 count=nullException in thread Thread-2 java.lang.NullPointerExceptionat com.fanqie.oms.thread.ThreadLocal.ThreadClient.run(ThreadClient.java

8、:21)当前线程名:Thread-2 count=nullpublic class ThreadClient extends Thread private static ThreadLocal count = new ThreadLocal() Override protected Integer initialValue() return 0; ; public void run() for(int i=0;i3;i+) System.out.println(当前线程名:+Thread.currentThread().getName() + count=+count.get(); count

9、.set(count.get()+1); try TimeUnit.SECONDS.sleep(1); catch (InterruptedException e) e.printStackTrace(); public static void main(String args) ThreadClient tc1 = new ThreadClient(); ThreadClient tc2 = new ThreadClient(); ThreadClient tc3 = new ThreadClient(); tc1.start(); tc2.start(); tc3.start(); 当前线

10、程名:Thread-0 count=0当前线程名:Thread-1 count=0当前线程名:Thread-2 count=0当前线程名:Thread-0 count=1当前线程名:Thread-2 count=1当前线程名:Thread-1 count=1当前线程名:Thread-0 count=2当前线程名:Thread-2 count=2当前线程名:Thread-1 count=2TimeUnitpublic void test(String args) throws InterruptedException System.out.println(Sleeping for 4 minut

11、es using Thread.sleep(); Thread.sleep(4 * 60 * 1000); System.out.println(Sleeping for 4 minutes using TimeUnit sleep(); TimeUnit.SECONDS.sleep(4); TimeUnit.MINUTES.sleep(4); TimeUnit.HOURS.sleep(1); TimeUnit.DAYS.sleep(1); ThreadLocal是什么 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以

12、独立地改变自己的副本,而不会影响其它线程所对应的副本。 对外提供的方法很简单 public T get() ; public void set(T value); public void remove();有利于jvm回收 protected T initialValue(); 返回null ThreadLocal并不能替代同步机制,两者面向的并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有资源的并发访问,是为了多个线程之间进行通信的有效方式;而效方式;而ThreadLoca

13、l是隔离多个线程的数据共享,是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所以,如果你样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则使用同步机制;需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序,使程序更,这将极大地简化你的程序,使程序更加易读、简洁。加易读、简洁。同步用时间换取空间,ThreadLocal用空间换取时间。自己实

14、现一个ThreadLocalpublic class MyThreadLocal private Map map = Collections.synchronizedMap(new HashMap(); public T get() Thread thread = Thread.currentThread(); T value = map.get(thread); if(value = null & !map.containsKey(thread) value = initialValue(); map.put(thread,value); return value; public void

15、set(T value) map.put(Thread.currentThread(),value); public void remove() map.remove(Thread.currentThread(); protected T initialValue() return null; 问题:性能低,内存无法释放1.MyThreadLocal中的map大小会随着线程的增加而增加,当并发存在时,效率会很低。2.当前线程结束后,实例依然存在,不会被GC回收,因为其他线程有可能依然还会在使用。源码实现源码实现public T get() Thread t = Thread.currentTh

16、read(); ThreadLocalMap map = getMap(t); if (map != null) ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; return setInitialValue(); 注意这里获取键值对传进去的是 this,而不是当前线程t。源码实现public void set(T value) Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != nu

17、ll) map.set(this, value); else createMap(t, value); 源码实现public void remove() ThreadLocalMap m = getMap(Thread.currentThread(); if (m != null) m.remove(this); static class ThreadLocalMappublic class ThreadLocal static class ThreadLocalMapstatic class Entry extends WeakReference Object value; Entry(Th

18、readLocal k, Object v) super(k); value = v; private Entry table; 不在ThreadLocal中,它应该在哪儿?public class Thread implements Runnable ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;ThreadLocalMap getMap(Thread t) return t.threadLocals; void createMap(Thread t, T firstValue) t.threadLocals = new

19、ThreadLocalMap(this, firstValue); 源码实现private T setInitialValue() T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; WeakReference在Java里, 当一个对象o被创建时, 它被放在Heap里. 当GC运行的时候, 如果发现没有任何

20、引用指向o, o就会被回收以腾出内存空间. 或者换句话说, 一个对象被回收, 必须满足两个条件: 1)没有任何引用指向它 2)GC被运行.Object c = new Car();c = null;WeakReferencepublic static void main(String args) test();public static void test() Student stu = new Student(张三, 12); WeakReference wr = new WeakReference(stu); int i = 1; while (true) if (wr.get() !=

21、null) System.out.println(执行第 + i + 次); i+; else System.out.println(对象stu被GC自动释放); System.out.println(wr.get(); break; .执行第176342次执行第176343次执行第176344次执行第176345次对象stu被GC自动释放nullWeakReference 另一作用:如果你想写一个 Java 程序,观察某对象什么时候会被垃圾收集的执行绪清除,你必须要用一个 reference 记住此对象,以便随时观察,但是却因此造成此对象的 reference 数目一直无法为零, 使得对象无

22、法被清除。 不过,现在有了 Weak Reference 之后,这就可以迎刃而解了。如果你希望能随时取得某对象的信息,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象,而不是用一般的 reference。通常用于Debug、内存监视等 package java.lang.ref; SoftReference 软引用 HardReference 强引用 PhantomReference 虚引用ThreadLocal实现流程ThreadLocal是如何为每个线程创建变量的副本的:首先,在每个线程Thread内部有一个ThreadLocal.ThreadLoca

23、lMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。Th

24、readLoal内存模型图如上图,如上图,ThreadLocalMap使用使用ThreadLocal的弱引用作为的弱引用作为key,如果一个,如果一个ThreadLocal没有外部强引用引用他,那么系统没有外部强引用引用他,那么系统GC的时候,这个的时候,这个ThreadLocal势必会被回收,这样一来,势必会被回收,这样一来,ThreadLocalMap中就会出现中就会出现key为为null的的Entry,就没有办法访问这些就没有办法访问这些key为为null的的Entry的的value,如果当前线程再迟迟不结束,如果当前线程再迟迟不结束的话,这些的话,这些key为为null的的Entry的

25、的value就会一直存在一条强引用链:就会一直存在一条强引用链:ThreadLocal Ref - Thread - ThreaLocalMap - Entry - value永远无法回收,造成内存泄露。永远无法回收,造成内存泄露。getEntryprivate Entry getEntry(ThreadLocal key) int i = key.threadLocalHashCode & (table.length - 1); Entry e = tablei; if (e != null & e.get() = key) return e; else return getEntryAft

26、erMiss(key, i, e); 整理一下ThreadLocalMap的getEntry函数的流程:首先从ThreadLocal的直接索引位置(通过ThreadLocal.threadLocalHashCode & (len-1)运算得到)获取Entry e,如果e不为null并且key相同则返回e;如果e为null或者key不一致则向下一个位置查询,如果下一个位置的key和当前需要查询的key相等,则返回对应的Entry,否则,如果key值为null,则擦除该位置的Entry,否则继续向下一个位置查询在这个过程中遇到的key为null的Entry都会被擦除,那么Entry内的value也

27、就没有强引用链,自然会被回收。仔细研究代码可以发现,set操作也有类似的思想,将key为null的这些Entry都删除,防止内存泄露。但是光这样还是不够的,上面的设计思路依赖一个前提条件:要调用ThreadLocalMap的genEntry函数或者set函数。这当然是不可能任何情况都成立的,所以很多情况下需要使用者手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露。所以JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以T

28、hreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。总结 在普通的同步机制中,是通过对象加锁来实现多个线程对统一变量的安全访问的,这时该变量是多个线程共享的,使用这种同步机制需要很细致的分析在什么时候对变量进行读写、什么时候需要锁定某个对象,什么时候释放该对象的锁等等。同步机制中一般使用synchronized关键字来保证同一时刻只有一个线程对共享变量进行操作。但在有些情况下,synchronized不能保证多线程对共享变量的正确读写。例如类有一个类变量,该类变量会被多个类方法读写,当多线

29、程操作该类的实例对象时,如果线程对类变量有读取、写入操作就会发生类变量读写错误,即便是在类方法前加上synchronized也无效,因为同一个线程在两次调用方法之间时锁是被释放的,这时其它线程可以访问对象的类方法,读取或修改类变量。 这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。总结 同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式; 而threadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享变量,这样当然不需要对多个线程进行同步了。 T

30、hreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。使用场景我们做web开发时的 web层的Action-业务逻辑层的Service-数据访问层的DAO,当我们要在这三层中共享参数时,那么我们就可以使用ThreadLocal 了。 1.在某个接口中定义一个静态的ThreadLocal 对象, 例如 public static ThreadLocal threadLocal=new ThreadLoc

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论