所谓可见性,指的是当一个线程修改了对象的状态后,其余线程可以看到该对象发生的变化。在单线程环境下,向某个变量写入值,而后在后面的操做再读取,在这个过程当中该变量的值对该线程来讲老是可见。可是,在多线程环境下,可见性就不必定等到保证,例如,对于一个共享变量 share = 0 来讲,线程1和线程2都进行share++ 操做,可是最终share 的结果并不必定是2。先看看一段代码html
public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread{ public void run() { while (!ready) { Thread.yield(); //当前线程从运行态->就绪态,从新竞争cpu } System.out.println(number); } } public static void main(String[] args) { new ReaderThread().start(); number = 42; ready = true; } }
上面的 NoVisibility 可能会一直循环下去(虽然这种状况发生的几率很小),由于 ReaderThread 线程一直看不到主线程对ready的更新;还有另外一种状况是输出结果多是0,有人会问有输出说明 ready 已经被更新为 true,那么 number 也应该被更新了42,看起来是这样,但在jvm执行指令时会出现“指令重排序”的现象。java
“指令重排序”指的是处理器为了提升程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行前后顺序同代码中的顺序一致,可是它会保证程序最终执行结果和代码顺序执行的结果是一致的。对于上面的代码,ready 和 number 谁先赋值对最终的程序结果并无影响(对于main线程来讲),故在实际执行可能先对 ready 赋值。可是要注意的是若是后面的语句对前面的语句存在依赖关系时,则不会发生“指令重排序”,例如数据库
int a = 2; //语句1 int r = 2; //语句2 a = a + 3; //语句3 r = a*a; //语句4
语句4 的 r 依赖于语句3的 a 的处理结果,故执行时语句4不能在语句3以前执行。安全
在上面 NoVisibility 产生错误的缘由是缺少同步使得 ReadTread 线程读取了失效数据。另外有一点,对于绝大多数基本变量的读取和写入都是原子操做,可是64位的数值变量除外(如long,double),这是由于对于64位的变量,jvm容许将64位的读或写操做分解为两个32位操做,当对该变量读和写操做在不一样线程进行,有可能会读取到当前值的高32位和从新赋值后的低32位,如线程1去读 long num 这个变量,还未开始读取,这时线程2对num这个变量从新赋值,先对低32位进行更新,还未更新高32位,这时线程1继续执行读取操做,因而线程1就读取了原来的高32位和更新后的低32位。多线程
那么怎么使得不一样线程不会读取到失效数据呢?一种简单的方式是加上内置锁,这样使得某一线程在还没有执行完同步代码块前,其余的线程没法执行同步代码块,这样就保证了每一个线程都能看到共享变量的最新值;另外的一种方式是把共享变量用 volatile 修饰,线程在读取volatile 变量时老是会返回最新写入的值,关于volatile更多详细请看下面参考连接http://www.javashuo.com/article/p-hsugcpqq-hw.html。jvm
不过这里有一个点要注意,volatile 虽然保证了变量的可见性,但并不保证原子性,这也是为何用 volatile 修饰的 int i 变量执行 i++ 操做仍不能保证线程安全,其实这要从i++ 这个操做的原理来说,i++包括三个操做:一、取 i 值;二、将 i+1 存入 tmp;三、i = tmp。在多线程对 valotile 修饰的 i 进行++操做时,先假设线程1执行完第2步后堵塞,这时线程2对 i 进行了更新,通知其余线程内存中的 i 已经被更新,大家应该从新取 i 值,但线程1在第3步并不须要取 i 值,而是将 tmp 值存入i。因此在使用volatile关键字是应该要记住它并不保证原子性。优化
什么是线程封闭?当咱们访问共享变量时,一般要使用同步,一种避免使用同步的方式就是不共享数据。很显然仅在单线程内访问数据,就不须要同步,这种避免共享数据的技术就是线程封闭。在java中,较经常使用到的线程封闭技术是栈封闭和使用 ThreadLocal 类。this
栈封闭:个人理解就是使用线程内部的局部变量。spa
ThreadLocal 类:这个要仔细讲讲,ThreadLocal 类为每一个线程保存了一份独立的副本,每次线程执行 ThreadLocal 的 get 或 set 方法都是每次以当前线程为参数去取当前线程对象里的 ThreadLocalMap,而 ThreadLocalMap 保存着以 ThreadLocal 对象为 key 的键值对,这样就使得每一个线程访问 ThreadLocal 变量互不干扰。先来看看ThreadLocal类怎么使用。线程
public class Test { private static ThreadLocal<Connection> conn = new ThreadLocal<Connection>() { // 重写 ThreadLocal类里的initialValue()方法 public Connection initialValue() { try { return DriverManager.getConnection("DB_URL");//取得某一数据库链接 } catch (SQLException e) { e.printStackTrace(); } return null; } }; public static Connection getConnection() { // 调用conn对象的get方法 return conn.get(); } }
下面看看ThreadLocal类的源码
先看看 get 方法
public ThreadLocal() { } public T get() { Thread t = Thread.currentThread(); /* * 查看当前线程t有没有相应的map,注意,该方法传入的参数为当前线程,
* 返回的是线程t的静态变量 threadLocals,该变量初始值为null,故对不一样
* 线程来讲,每一个线程都有本身的threadLocals */ ThreadLocalMap map = getMap(t); /*
* ThreadLocalMap getMap(Thread t) { * return t.threadLocals; * } */ if (map != null) { // 获取当前线程下对象的value,注意,每一个线程都存有当前对象的value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 若是线程是第一次调用ThreadLocal的get方法,那么返回值从重写的初始化方法获得 return setInitialValue(); }
去看看 setInitialValue() 方法
private T setInitialValue() { // 初始化value T value = initialValue(); Thread t = Thread.currentThread(); // 取当前线程的map ThreadLocalMap map = getMap(t); if (map != null) // 不为空,更新当前线程下该对象的value map.set(this, value); else // map为空,建立map createMap(t, value); return value; }
看看 ThreadLocal 怎么建立 ThreadLocalMap 的
// 这个方法是使得ThreadLocal类保存线程本地变量的关键,它新建的ThreadLocalMap是以当前ThreadLocal对象为key,而后是存在该线程的静态变量threadLocals里。 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 对于table里的引用,每次都是new出来了,故不会和其余线程指向同一个当前对象 table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
看到这里,相信你们就已经大概明白ThreadLocal类线程封闭的具体原理了。
以上内容若有不当之处,请指出。谢谢!
参考连接: