java线程封闭的方法是什么-创新互联

本篇内容主要讲解“java线程封闭的方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java线程封闭的方法是什么”吧!

坚守“ 做人真诚 · 做事靠谱 · 口碑至上 · 高效敬业 ”的价值观,专业网站建设服务10余年为成都会所设计小微创业公司专业提供企业网站设计营销网站建设商城网站建设手机网站建设小程序网站建设网站改版,从内容策划、视觉设计、底层架构、网页布局、功能开发迭代于一体的高端网站建设服务。

线程封闭的概念

访问共享变量时,通常要使用同步,所以避免使用同步的方法就是减少共享数据的使用,这种技术就是线程封闭。

实现线程封闭的方法

1:ad-hoc线程封闭

这是完全靠实现者控制的线程封闭,他的线程封闭完全靠实现者实现。也是最糟糕的一种线程封闭。所以我们直接把他忽略掉吧。

2:栈封闭

栈封闭是我们编程当中遇到的最多的线程封闭。什么是栈封闭呢?简单的说就是局部变量。多个线程访问一个方法,此方法中的局部变量都会被拷贝一分儿到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。

3:ThreadLocal封闭

使用ThreadLocal是实现线程封闭的好方法,有兴趣的朋友可以研究一下ThreadLocal的源码,其实我们可以理解ThreadLocal内部维护了一个Map,Map的key是每个线程的名称,而Map的值就是我们要封闭的对象。每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用Map实现了对象的线程封闭。

线程封闭详解

线程封闭:当访问共享的可变数据时,通常需要同步。一种避免同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步,这种技术称为线程封闭(thread confinement)

线程封闭技术一个常见的应用就是JDBC的Connection对象,JDBC规范并没有要求Connection对象必须是线程安全的,在服务器应用程序中,线程从连接池获取一个Connection对象,使用完之后将对象返还给连接池。下面介绍几种线程封闭技术:

1、Ad-hoc线程封闭

Ad-hoc线程封闭是指,维护线程的封闭性的职责完全由程序实现承担,是非常脆弱的,因此在程序中尽量少使用,一般使用更强的线程封闭技术,比如栈封闭或者ThreadLocal类。

2、栈封闭 

栈封闭是线程封闭的一种特列,在栈封闭中,只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行栈中,其他线程无法访问这个栈,栈封闭也称为线程内部使用或者线程局部使用。简单的说就是局部变量。多个线程访问一个方法,此方法中的局部变量都会被拷贝一分儿到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。

比如下面的例子:

public int loadTheArk(Collection candidates) {     SortedSet animals;     int numPairs = 0;     Animal candidate = null;          //animals被封装在方法中,不要使它们溢出     animals = new TreeSet(new SpeciesGenderComparator());     animals.addAll(candidates);     for(Animal a:animals){       if(candidate==null || !candidate.isPotentialMate(a)){         candidate = a;       }else{         ark.load(new AnimalPair(candidate,a));         ++numPairs;         candidate = null;       }     }     return numPairs; }

在loadTheArk中实例化一个TreeSet对象,并将该对象的一个引用保存到animals中。此时,只有一个引用指向集合animals,这个引用被封闭到局部变量中,因此也被封闭到局部变量中。然而,如果发布了对集合animals(或者该对象中的任何内部数据)的引用,那么封闭性将被破坏,并导致对象animals的逸出。

3、ThreadLocal类

维持线程封闭性的一种更加规范方法是使用ThreadLocal类,这个类能使线程中某个值与保存值的对象关联起来。ThreadLocal类提供了get和set等访问接口或者方法,这些方法为每个使用该变量的线程都存在一份独立的副本,因此get总是放回当前执行线程在调用set设置的新值。看一下下面代码例子:

public class ConnectionManager {    private static ThreadLocal connectionHolder = new ThreadLocal() {     public Connection initialValue() {       Connection conn = null;       try {         conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password");       } catch (SQLException e) {         e.printStackTrace();       }       return conn;     }   };    public static Connection getConnection() {     return connectionHolder.get();   }    public static void setConnection(Connection conn) {     connectionHolder.set(conn);   } }

通过调用ConnectionManager.getConnection()方法,每个线程获取到的,都是自己独立拥有的一个的Connection对象副本,第一次获取时,是通过initialValue()方法的返回值来设置值的。通过ConnectionManager.setConnection(Connection conn)方法设置的Connection对象,也只会和当前线程绑定。这样就实现了Connection对象在多个线程中的完全隔离。在Spring容器中管理多线程环境下的Connection对象时,采用的思路和以上代码非常相似。

每个线程是怎么和Connection对象副本绑定的?这个对象副本保存在哪里。当某个线程初次调用ThreadLocal类的get方法时,就会调用initialValue来获取初始值,从概念上看,我们可以将ThreadLocal视为包含了Map对象,其中保存了特定于该线程的值,但是ThreadLocal的实现并非如此,这样只是为了我们方便理解而已。

下面我们来分析一下ThreadLocal类的源码。ThreadLocal类的方法很简单,只有四个,分别为set,get,remove, initialValue,从字面上我们也能理解这些方法的作用。

public T get():返回当前线程所对应的局部变量。

public void set(T arg0):设置当前线程局部变量的值。  

public void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。注意,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

protected T initialValue(): 对当线程局部变量进行初始化,并返回该初始值。是protected 属性,显然是让子类进行对其覆盖重写的,只有第一次调用set和get方法时才调用。  

下面我们对这四个方法的源码进行分析,看看ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”。

3.1 set方法

以下是set方法的源码

public void set(T arg0) {    Thread arg1 = Thread.currentThread();    ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);    if (arg2 != null) {      arg2.set(this, arg0);    } else {      this.createMap(arg1, arg0);    }  }

从set方法中可以看到,首先获取当前线程:Thread arg1 = Thread.currentThread();

再获取当前线程的ThreadLocalMap:ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);

判断ThreadLocalMap是否为空,不为空,则以键值对的形式设置值,key为this,value就是局部变量的副本,this是当前线程持有的ThreadLocal类实例化对象。

假如为空,则通过createMap方法创建。

我们看下getMap和createMap方法的源码:

ThreadLocal.ThreadLocalMap getMap(Thread arg0) {    return arg0.threadLocals;}void createMap(Thread arg0, T arg1) {    arg0.threadLocals = new ThreadLocal.ThreadLocalMap(this, arg1);  }

从代码上已经写的非常清楚,每个线程都有自己的局部变量的副本,该副本是存在ThreadLocalMap 中,其中键值就是ThreadLocal类实例化对象。也就是说每个线程都拥有自己的ThreadLocalMap,ThreadLocalMap保存的就是局部变量副本。我们看一下java.lang.Thread源码。

private static int threadInitNumber;ThreadLocalMap threadLocals = null;ThreadLocalMap inheritableThreadLocals = null;

3.2 get方法

public T get() {    Thread arg0 = Thread.currentThread();    ThreadLocal.ThreadLocalMap arg1 = this.getMap(arg0);    if (arg1 != null) {      ThreadLocal.ThreadLocalMap.Entry arg2 = arg1.getEntry(this);      if (arg2 != null) {        Object arg3 = arg2.value;        return arg3;      }    }    return this.setInitialValue();}

从代码上看,前两步和set方法是一个样的,分别获取当前线程和当前线程的ThreadLocalMap,第三步判断ThreadLocalMap是否为空,不为空根据this键值获取value,为空调用setInitialValue()方法。

以下是setInitialValue方法代码:

private T setInitialValue() {    Object arg0 = this.initialValue();    Thread arg1 = Thread.currentThread();    ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);    if (arg2 != null) {      arg2.set(this, arg0);    } else {      this.createMap(arg1, arg0);    }    return arg0;}

在setInitialValue里调用了initialValue()方法,也就是子类要重写覆盖的方法,对应上面的例子的代码是:

protected Connection initialValue() {       Connection conn = null;       try {         conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password");       } catch (SQLException e) {         e.printStackTrace();       }       return conn; }

然后获取当前线程和当前线程的ThreadLocalMap,ThreadLocalMap为空则调用createMap,否则调用set方法。

到此,相信大家对“java线程封闭的方法是什么”有了更深的了解,不妨来实际操作一番吧!这里是创新互联建站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!


文章名称:java线程封闭的方法是什么-创新互联
本文地址:http://scyanting.com/article/dgjeoo.html