Java基础加强之多线程篇 - 线程创建与终止、互斥、通信、本地变量

swiftchen 5年前
   <h2>线程创建与终止</h2>    <h2>线程创建</h2>    <h3>Thread类与 Runnable 接口的关系</h3>    <pre>  <code class="language-java">public interface Runnable {          public abstract void run();  }    public class Thread implements Runnable {      /* What will be run. */          private Runnable target;          ......          /**           * Causes this thread to begin execution; the Java Virtual Machine           * calls the <code>run</code> method of this thread.           */          public synchronized void start() {......}            ......      @Override      public void run() {          if (target != null) {              target.run();          }          }          ......  }  </code></pre>    <p>Thread类与 Runnable接口 都位于java.lang包中。 从上面我们可以看出,Runnable接口中只定义了run()方法,Thread类实现了Runnable 接口并重写了run()方法。 当调用Thread 类的 start()方法时,实际上Java虚拟机就去调用Thread 类的 run()方法 ,而 Thread 类的 run()方法 中最终调用的是 Runnable 类型对象的run()方法 。</p>    <h3>继承 Thread并重写 run 方法</h3>    <pre>  <code class="language-java">public class ThreadTest1 extends Thread {      @Override      public void run() {          while(true) {              try {                  Thread.sleep(1000);              } catch (InterruptedException e) {                  e.printStackTrace();              }              System.out.println("thread 1:" + Thread.currentThread().getName());          }      }        public static void main(String[] args) {          ThreadTest1 thread = new ThreadTest1 ();          thread.start();      }//main end  }  </code></pre>    <p>可以写成内部类的形式, new Thread(){ @Override run(...) }.start();</p>    <h3>实现 Runnable接口并重写 run 方法</h3>    <pre>  <code class="language-java">public class ThreadTest2  implements Runnable {      @Override      public void run() {          while(true) {              try {                  Thread.sleep(1000);              } catch (InterruptedException e) {                  e.printStackTrace();              }              System.out.println("thread 3:" + Thread.currentThread().getName());          }      }      public static void main(String[] args) {          ThreadTest2  thread3 = new ThreadTest2();          Thread thread = new Thread(thread3);          thread.start();      }//main end  }  </code></pre>    <p>可以写成内部类的形式, new Thread( new Runnable(){@Override run(...)} ).start();</p>    <h2>线程终止</h2>    <p>当调用 Thread类的 start() 方法时,将会创建一个线程,这时刚创建的线程处于就绪状态(可运行状态),并没有运行,处于就绪状态的线程就可以等 JVM 调度。当 JVM 调度该线程时,该线程进入运行状态,即执行 Thread 类的 run() 方法中的内容。 run() 方法执行完,线程结束,线程进入死亡状态。这是线程自然终止的过程,我们也可以通过 Thread 类提供的一些方法来终止线程。</p>    <h3>interrupt()\isInterrupted()\interrupted() 方法介绍</h3>    <p>stop() 方法没有做任何的清除操作就粗暴终止线程,释放该线程所持有的对象锁(下文将介绍),受该对象锁保护的其它对象对其他线程可见,因此具有不安全性。</p>    <p>suspend() 方法会使目标线程会停下来,但仍然持有在这之前获得的对象锁,对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。</p>    <p>终上所述, 不建议使用stop()方法和 suspend() 方法来终止线程,通常我们通过 interrupt() 方法来终止处于阻塞状态和运行状态的线程 。</p>    <p>需要注意的是, interrupt()方法不会中断一个正在运行的线程,仅仅是将线程的中断标记设为 true ,当调用了阻塞方法之后,线程会不断监听中断标志,如果为true,则产生一个 InterruptedException 异常,将 InterruptedException 放在 catch 中就能终止线程。</p>    <p>isInterrupted()方法可以返回中断标记 ,常用循环判断条件。</p>    <p>interrupted()方法测试当前线程是否已经中断,线程的中断标志由该方法清除。 interrupted()除了返回中断标记之外,它还会清除中断标记 。</p>    <h3>interrupt() 用法</h3>    <p>看下面例子</p>    <pre>  <code class="language-java">public class ThreadInterruptedTest extends Thread {      @Override      public void run() {              try {                  int i = 0;                  while(!isInterrupted()) {                      i ++ ;                      Thread.sleep(1000);                      System.out.println(this.getName() + " is looping,i=" + i);                  }              } catch (InterruptedException e) {                  System.out.println(this.getName() +                           " catch InterruptedException,state:" + this.getState());                    e.printStackTrace();              }      }        public static void main(String[] args) throws Exception {                    ThreadInterruptedTest thread = new ThreadInterruptedTest();          System.out.println(thread.getName()                   + " state:" + thread.getState());                      thread.start();          System.out.println(thread.getName()                   + " state:" + thread.getState());                      Thread.sleep(5000);                    System.out.println("flag: " + thread.isInterrupted());                    //发出中断指令          thread.interrupt();                    System.out.println("flag: " + thread.isInterrupted());                    System.out.println(thread.getName()                   + " state:" + thread.getState());                      System.out.println(thread.interrupted());      }  }  </code></pre>    <p>运行结果</p>    <pre>  <code class="language-java">Thread-0 state:NEW  Thread-0 state:RUNNABLE  Thread-0 is looping,i=1  Thread-0 is looping,i=2  Thread-0 is looping,i=3  Thread-0 is looping,i=4  flag: false  flag: true  Thread-0 state:TIMED_WAITING  Thread-0 catch InterruptedException,state:RUNNABLE  false  java.lang.InterruptedException: sleep interrupted      at java.lang.Thread.sleep(Native Method)      at com.itpsc.thread.ThreadInterruptedTest.run(ThreadInterruptedTest.java:11)  </code></pre>    <p>从运行结果可以看出,调用 interrupt() 发出中断指令前,中断标志位 false ,发出中断指令后中断标志位为 true ,而调用 interrupted() 方法后则中断标志被清除。从发出的异常来看,是在一个 sleep interrupted ,且发出异常后线程被唤醒,以便线程能从异常中正常退出。</p>    <h2>线程运行状态图</h2>    <p>线程从创建到终止可能会经历各种状态。在 . . .State类的源码中,可以看到线程有以下几种状态: NEW 、 RUNNABLE 、 BLOCKED 、 WAITING 、 TIMED_WAITING 、 TERMINATED 。各种状态的转换如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ed6f9dbaa3d2ea5973045fe251db72de.png">  </p>    <p>当通过 Thread t = new Thread()方式创建线程时,线程处于新建状态;当调用 t.start() 方法时,线程进入可运行状态(注意,还没有运行);处于可运行状态的线程将在适当的时机被 CPU 资源调度器调度,进入运行状态,也就是线程执行 run() 方法中的内容; run() 方法执行完或者程序异常退出线程进入终止状态。线程从运行状态也有可能进入阻塞状态,如调用 wait() 方法后进入等待对象锁(下文将介绍),调用 sleep() 方法后进行入计时等待。</p>    <h2>线程互斥</h2>    <p>现在我们已经知道线程的创建与终止了。互斥,是指系统中的某些共享资源,一次只允许一个线程访问,当一个线程正在访问该临界资源时,其它线程必须等待。</p>    <h2>对象锁</h2>    <p>在 java中, 每一个对象有且仅有一个锁,锁也称为对象监视器 。通过对象的锁,多个线程之间可以实现对某个方法(临界资源)的互斥访问。那么,如何获取对象的锁呢?当我们 调用对象的synchronized修饰的方法或者 synchronized 修饰的代码块时,锁住的是对象实例,就获取了该对象的锁 。</p>    <h2>全局锁</h2>    <p>Java中有实例对象也有类对象,竟然有对象锁,那么久有类锁,也称 全局锁 。 当synchronized修饰静态方法或者静态代码块时,锁住的是该类的 Class 实例(字节码对象),获取的便是该类的全局锁 。看下面获取对象锁实现线程互斥的两种方式。</p>    <h2>线程互斥的两种方式</h2>    <p>先看下面这个没有实现线程互斥的例子。</p>    <pre>  <code class="language-java">public class SynchronizedTest {        public static void main(String[] args) {          new SynchronizedTest().init();      }            private void init() {          final Outputer output = new Outputer();          //线程1打印"hello,i am thread 1"          new Thread(new Runnable(){              @Override              public void run() {                  while(true) {                       try{                           Thread.sleep(1000);                       }catch(InterruptedException e) {                           e.printStackTrace();                       }                       output.output("hello,i am thread 1");                  }                  }          }).start();                    //线程2打印"hello,i am thread 2"          new Thread(new Runnable(){              @Override              public void run() {                  while(true) {                       try{                           Thread.sleep(1000);                       }catch(InterruptedException e) {                           e.printStackTrace();                       }                       output.output("hello,i am thread 2");                  }              }          }).start();      }            class Outputer {          public void output(String name) {              for(int i=0; i<name.length(); i++) {                  System.out.print(name.charAt(i));              }              System.out.println();          }      }  }  </code></pre>    <p>运行结果</p>    <pre>  <code class="language-java">hello,i am thread 1  hello,i am thread 2  hello,i am hellthread 1  o,i am thread 2  hello,i am thread 2  hello,i am thread 1  hello,i am thread 2  hello,i am threadhel 2lo,i am thread   1  </code></pre>    <p>线程 1和线程2同时调用 进行输出,从运行结果可以看出,线程之间没有执行完各自的输出任务就被交替了运行了 。下面通过对象的锁实现线程1和线程2对output方法的互斥访问。</p>    <h3>synchronized 修饰方法</h3>    <p>使用 synchronized 对 output 方法进行修饰,可以让调用者获得锁。 synchronized 修饰方法没有显示声明锁的对象,默认是当前方法所在类的对象 this 。</p>    <pre>  <code class="language-java">public synchronized void output(String name) {      for(int i=0; i<name.length(); i++) {          System.out.print(name.charAt(i));      }      System.out.println();  }    </code></pre>    <h3>synchronized 修饰代码块</h3>    <p>使用 synchronized 对 output 方法中的代码块进行修饰,也可以让调用者获得锁。</p>    <pre>  <code class="language-java">public void output(String name) {      synchronized(this){          for(int i=0; i<name.length(); i++) {              System.out.print(name.charAt(i));          }          System.out.println();      }  }   </code></pre>    <p>使用 synchronized之后,线程 1 和线程 2 对 output 方法实现了互斥访问。</p>    <pre>  <code class="language-java">hello,i am thread 1  hello,i am thread 2  hello,i am thread 1  hello,i am thread 2  hello,i am thread 1  hello,i am thread 2  hello,i am thread 1  </code></pre>    <h2>synchronized 用法</h2>    <p>先看下面的例子,我们来总结下 synchronized 的一些常用用法。</p>    <pre>  <code class="language-java">public class SynchronizedTest {        public static void main(String[] args) {          new SynchronizedTest().init();      }            private void init() {          final Outputer output = new Outputer();          //线程1打印"hello,i am thread 1"          new Thread(new Runnable(){              @Override              public void run() {                  output.output("hello,i am thread 1");              }          }).start();                    //线程2打印"hello,i am thread 2"          new Thread(new Runnable(){              @Override              public void run() {                  output.output("hello,i am thread 2");              }          }).start();      }            static class Outputer {          public synchronized void output(String name) {              for(int i=0; i<5; i++) {                  try {                      Thread.sleep(1000);                  } catch (InterruptedException e) {                      e.printStackTrace();                  }                  System.out.println(name);              }          }                    public void output2(String name) {              synchronized(this) {                  for(int i=0; i<5; i++) {                      try {                          Thread.sleep(1000);                      } catch (InterruptedException e) {                          e.printStackTrace();                      }                      System.out.println(name);                  }              }          }                    public void output3(String name) {              for(int i=0; i<5; i++) {                  try {                      Thread.sleep(1000);                  } catch (InterruptedException e) {                      e.printStackTrace();                  }                  System.out.println(name);              }          }                    public static synchronized void output4(String name) {              for(int i=0; i<5; i++) {                  try {                      Thread.sleep(1000);                  } catch (InterruptedException e) {                      e.printStackTrace();                  }                  System.out.println(name);              }          }                    public void output5(String name) {              synchronized(Outputer.class) {                  for(int i=0; i<5; i++) {                      try {                          Thread.sleep(1000);                      } catch (InterruptedException e) {                          e.printStackTrace();                      }                      System.out.println(name);                  }              }          }      }  }  </code></pre>    <p>运行结果</p>    <pre>  <code class="language-java">hello,i am thread 1  hello,i am thread 1  hello,i am thread 1  hello,i am thread 1  hello,i am thread 1  hello,i am thread 2  hello,i am thread 2  hello,i am thread 2  hello,i am thread 2  hello,i am thread 2  </code></pre>    <p>线程 1和线程 2 同时访问 output 对象的 synchronized 修饰的 output 方法,即两个线程竞争的是 output 对象的锁,这是同一个锁,所以当线程 1 在持有锁的时候,线程 2 必须等待,即下面的用法 1 。</p>    <p>用法 1</p>    <p>当一个线程访问某个对象的 synchronized 方法或者 synchronized 代码块时, 其它线程 对该对象的 该synchronized 方法或者 synchronized 代码块 的访问将阻塞。</p>    <p>用法 2</p>    <p>当一个线程访问某个对象的 synchronized 方法或者 synchronized 代码块时, 其它线程 对该对象的 其他synchronized 方法或者 synchronized 代码块 的访问将阻塞。</p>    <p>修该上面的 SynchronizedTest 例子,线程 1 访问 output 方法,线程 2 访问 output2 方法,运行结果同上,因为 output 方法 和 output2 方法都属于同一个对象 output ,因此线程 1 和线程 2 竞争的也是同一个锁。</p>    <p>用法 3</p>    <p>当一个线程访问某个对象的 synchronized 方法或者 synchronized 代码块时, 其它线程仍然可以 对该对象的 其他非synchronized 方法或者 synchronized 代码块 访问。</p>    <p>修该上面的 SynchronizedTest 例子,线程 1 访问 output 方法,线程 2 访问 output3 方法,运行结果是线程 1 和线程 2 交替输出。结果显而易见,线程 2 访问 output3 方法并不是 synchronized 修饰的 output 方法或者代码块,线程 2 并不需要持有锁,因此线程 1 的运行不会阻塞线程 2 的运行。</p>    <p>用法 4</p>    <p>当 synchronized 修饰静态方法时,锁住的是该类的 Class 实例(字节码对象)。修该上面的 SynchronizedTest 例子,线程 1 访问 output4 方法,线程 2 访问 output5 方法,运行结果同用法 1 ,说明线程 1 和线程 2 竞争的是 Outputer 类的 Class 实例(字节码对象)的锁。</p>    <h2>线程通信</h2>    <p>多个线程之间往往需要相互协作来完成某一个任务, synchronized 和对象锁能实现线程互斥,但是不能实现线程通信 。</p>    <h2>wait()\notify()\notifyAll() 介绍</h2>    <p>线程之间的通信通过 java.lang 包中Object类中的 wait()方法和notify()、notifyAll()等方法进行。我们知道, Java 中 每个对象都有一个锁 , wait() 方法用于等待对象的锁, notify()、notifyAll() 方法用于通知其他线程对象锁可以使用。</p>    <p>wait()\notify()\notifyAll()依赖于对象锁,对象锁是对象所持有,Object类是所有java类的父类,这样每一个java类(对象)都有线程通信的基本方法 。这就是这些方法定义在Object类中而不定义在Thread类中的原因。</p>    <p>wait()方法的会让当前线程释放对象锁并进入等待对象锁的状态,当前线程是指正在cpu上运行的线程。当前线程调用notify()\notifyAll()后,等待对象锁的线程将被唤醒。</p>    <p>调用 wait()方法或者notify()方法的对象必须和对象锁所属的对象是同一个对象,并且必须在synchronized方法或者synchronized代码块中被调用。</p>    <h2>yieId() 介绍</h2>    <p>yieId()的作用是给线程调度器一个提示,告知线程调度器当前线程愿意让出 CPU ,但是线程调度器可以忽略这个提示。因此, yieId()的作用仅仅是告知线程调度器当前线程愿意让出 CPU 给其他线程执行(竟然只是愿意,当前线程可以随时反悔,那其他线程也不一定能得到 CPU 执行),而且不会让当前线程释放对象锁 。</p>    <p>yieId()能让当前线程由运行状态进入到就绪状态,从而让其它具有相同优先级的等待线程获取执行权。但是,并不能保证在当前线程调用 yield() 之后,其它具有相同优先级的线程就一定能获得执行权,也有可能当前线程又进入到运行状态继续运行。</p>    <p>yieId() 只建议在测试环境中使用。</p>    <p>wait()和 yield() 的区别</p>    <p>( 1) wait() 是让线程由运行状态进入到等待 ( 阻塞 ) 状态,而 yield() 是让线程由运行状态进入到就绪状态。</p>    <p>( 2) wait() 是让线程释放它所持有对象的锁,而 yield() 方法不会释放锁。</p>    <h2>多线程交替输出及 volatile 应用</h2>    <p>下面的例子是 “主线程输出三次接着子线程输出三次”,重复两次。</p>    <pre>  <code class="language-java">public class WaitnotifyTest {            public static volatile boolean shouldChildren = false;            public static void main(String[] args) throws Exception{          final Outputer outputer = new Outputer();                    //创建子线程          Thread chrild = new Thread(new Runnable(){              @Override              public void run() {                  try {                      for(int i=0;i<2;i++)                          outputer.children();                  } catch (Exception e) {                      e.printStackTrace();                  }              }          });          chrild.start();          //主线程          for(int i=0;i<2;i++)              outputer.main();      }  }      class Outputer {      //子线程循环输出      public synchronized void children() throws Exception{          while(!WaitnotifyTest.shouldChildren) {              System.out.println(Thread.currentThread().getName()                      + " thread end loop,go to waitting");              //子线程进入等待状态              this.wait();          }                    System.out.println(Thread.currentThread().getName()                  + " thread start loop");          for(int i=1; i<=3; i++) {              System.out.println("hello,i am chrildren thread,loop:" + i);          }                    WaitnotifyTest.shouldChildren = false;          //唤醒主线程          this.notify();      }            //主线程循环输出      public synchronized void main() throws Exception{          while(WaitnotifyTest.shouldChildren) {              System.out.println(Thread.currentThread().getName()                      + " thread end loop,go to waitting");              //主线程进入等待状态              this.wait();          }                    System.out.println(Thread.currentThread().getName()                  + " thread start loop");          for(int i=1; i<=3; i++) {              System.out.println("hello,i am main thread,loop:" + i);          }                    WaitnotifyTest.shouldChildren = true;          //唤醒子线程          this.notify();      }  }  </code></pre>    <p>运行结果</p>    <pre>  <code class="language-java">main thread start loop  hello,i am main thread,loop:1  hello,i am main thread,loop:2  hello,i am main thread,loop:3  main thread end loop,go to waitting  Thread-0 thread start loop  hello,i am chrildren thread,loop:1  hello,i am chrildren thread,loop:2  hello,i am chrildren thread,loop:3  Thread-0 thread end loop,go to waitting  main thread start loop  hello,i am main thread,loop:1  hello,i am main thread,loop:2  hello,i am main thread,loop:3  Thread-0 thread start loop  hello,i am chrildren thread,loop:1  hello,i am chrildren thread,loop:2  hello,i am chrildren thread,loop:3  </code></pre>    <p>volatile修饰 shouldChildren,线程直接读取shouldChildren变量并且不缓存它,修改了shouldChildren 立马让其他线程可见,这就确保线程读取到的变量是一致的。</p>    <h2>线程本地变量</h2>    <h2>线程本地变量</h2>    <p>线程本地变量,可能称为 线程局部变量 更容易理解,即为 每一个使用该变量的线程都提供一个变量值的副本 ,相当于将变量的副本绑定到线程中,每一个线程可以独立地修改自己的变量副本,而不会和其它线程的变量副本冲突。 在线程消失之后,线程局部变量的所有副本都会被垃圾回收 (下面的源码分析中将提到) 。</p>    <h2>ThreadLocal 实现分析</h2>    <p>ThreadLocal</p>    <p>在 java.lang.Thread类中,有一个 ThreadLocal.ThreadLocalMap类型的变量 threadLocals ,这个变量就是用来存储线程局部变量 的。</p>    <pre>  <code class="language-java">/* ThreadLocal values pertaining to this thread. This map is maintained   * by the ThreadLocal class. */  ThreadLocal.ThreadLocalMap threadLocals = null;   </code></pre>    <p>下面我们重点分析 ThreadLocal的内部实现。 ThreadLocal 也位于 java.lang 包中。其主要成员有:</p>    <pre>  <code class="language-java">public T get() {}  private T setInitialValue() {}  public void set(T value) {}  private void remove(ThreadLocal key) {}  ThreadLocalMap getMap(Thread t){}  void createMap(Thread t, T firstValue) {}  static class ThreadLocalMap {}   </code></pre>    <p>Set</p>    <p>我们从 set方法开始。 Set 方法源码如下</p>    <pre>  <code class="language-java">/**   * Sets the current thread's copy of this thread-local variable   * to the specified value.  Most subclasses will have no need to   * override this method, relying solely on the {@link #initialValue}   * method to set the values of thread-locals.   *   * @param value the value to be stored in the current thread's copy of   *        this thread-local.   */  public void set(T value) {      Thread t = Thread.currentThread();      ThreadLocalMap map = getMap(t);      if (map != null)          map.set(this, value);      else          createMap(t, value);  }    </code></pre>    <p>先获取当前的线程,然后通过 getMap(t)方法获取到一个 map , map 的类型为 ThreadLocalMap 。</p>    <p>这个 map其实就是存储线程变量的对象 threadLocals 。 ThreadLocalMap是 ThreadLocal 中的一个内部类,是一个定制的 hashmap 以便适用于存储线程本地变量 。竟然是定制的hashmap,那么就有 Entry 和 table ( hashmap 的内部实现参考上一篇: <a href="/misc/goto?guid=4959727942808653158" rel="nofollow,noindex"> Java基础加强之集合篇(模块记忆、精要分析) </a> )。而 ThreadLocalMap中的 Entry 继承了 WeakReference ,弱引用是不能保证不被垃圾回收器回收的 ,这就是前文提到的在线程消失之后,线程局部变量的所有副本都会被垃圾回收。此外,Entry 中使用 ThreadLocal 作为 key ,线程局部变量作为 value 。如果 threadLocals 不为空,则设值否者调用 createMap 方法创建 threadLocals 。 注意设值的时候传的是this而不是当前线程 t 。</p>    <pre>  <code class="language-java">/**   * ThreadLocalMap is a customized hash map suitable only for   * maintaining thread local values. No operations are exported   * outside of the ThreadLocal class. The class is package private to   * allow declaration of fields in class Thread.  To help deal with   * very large and long-lived usages, the hash table entries use   * WeakReferences for keys. However, since reference queues are not   * used, stale entries are guaranteed to be removed only when   * the table starts running out of space.   */  static class ThreadLocalMap {        /**       * The entries in this hash map extend WeakReference, using       * its main ref field as the key (which is always a       * ThreadLocal object).  Note that null keys (i.e. entry.get()       * == null) mean that the key is no longer referenced, so the       * entry can be expunged from table.  Such entries are referred to       * as "stale entries" in the code that follows.       */      static class Entry extends WeakReference<ThreadLocal> {          /** The value associated with this ThreadLocal. */          Object value;            Entry(ThreadLocal k, Object v) {              super(k);              value = v;          }      }   </code></pre>    <p>接下来我们看看 createMap 方法</p>    <pre>  <code class="language-java">/**   * Create the map associated with a ThreadLocal. Overridden in   * InheritableThreadLocal.   *   * @param t the current thread   * @param firstValue value for the initial entry of the map   * @param map the map to store.   */  void createMap(Thread t, T firstValue) {      t.threadLocals = new ThreadLocalMap(this, firstValue);  }   </code></pre>    <p>createMap方法其实就是为当前线程的 threadLocals 变量分配空间并存储线程的第一个变量。现在我们已经知道线程是如何初始化并设值自己的局部变量了,下面我们看看取值。</p>    <p>Get</p>    <pre>  <code class="language-java">/**   * Returns the value in the current thread's copy of this   * thread-local variable.  If the variable has no value for the   * current thread, it is first initialized to the value returned   * by an invocation of the {@link #initialValue} method.   *   * @return the current thread's value of this thread-local   */  public T get() {      Thread t = Thread.currentThread();      ThreadLocalMap map = getMap(t);      if (map != null) {          ThreadLocalMap.Entry e = map.getEntry(this);          if (e != null)              return (T)e.value;      }      return setInitialValue();  }    </code></pre>    <p>先获取当前的线程,然后通过 getMap(t)方法获取当前线程存变量的对象 threadLocals ,如果 threadLocals 不为空则取值并返回( 注意传入的key是 this 对象而不是当前线程 t ),否则调用setInitialValue方法初始化。 setInitialValue 和 set 方法唯一不同的是调用了 initialValue 进行初始化,也就是在获取变量之前要初始化。</p>    <pre>  <code class="language-java">/**   * Variant of set() to establish initialValue. Used instead   * of set() in case user has overridden the set() method.   *   * @return the initial value   */  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;  }    </code></pre>    <p>总的来讲,每创建一个线程( Thread对象),该线程即拥有存储线程本地变量的 threadLocals 对象, threadLocals 对象初始为 null ,当通过 ThreadLocal 对象调用 set/get 方法时,就会对线程的 threadLocals 对象进行初始化,并且以当前 ThreadLocal 对象为键值,以 ThreadLocal 要保存的变量为 value ,存到 threadLocals 。看下面的例子。</p>    <h2>ThreadLocal 应用</h2>    <pre>  <code class="language-java">  public class ThreadLocalShareVariable {            public static void main(String[] args) {          //创建3个线程          for(int i=0; i<3;i++) {              //创建线程              new Thread(new Runnable(){                  @Override                  public void run() {                      //线程设置自己的变量                      int age = new Random().nextInt(100);                      String name = getRandomString(5);                      System.out.println("Thread " + Thread.currentThread().getName()                               + " has put data:" + name + " " + age);                                            //存储与当前线程有关的变量                      Passenger.getInstance().setName(name);                      Passenger.getInstance().setAge(age);                                            //线程访问共享变量                      new ModuleA().getData();                      new ModuleB().getData();                  }              }).start();          }      }            static class ModuleA {          public void getData(){              //获取与当前线程有关的变量              String name = Passenger.getInstance().getName();              int data = Passenger.getInstance().getAge();              System.out.println("moduleA get data from "               + Thread.currentThread().getName() + ":" + name + " "+ data);          }      }            static class ModuleB {          public void getData(){              //获取与当前线程有关的变量              String name = Passenger.getInstance().getName();              int data = Passenger.getInstance().getAge();              System.out.println("moduleB get data from "               + Thread.currentThread().getName() + ":" + name + " "+ data);          }      }            /**       * 随机生成字符串       * @param length       * @return       */      public static String getRandomString(int length){          final String str = "abcdefghijklmnopqrstuvwxyz";           StringBuffer sb = new StringBuffer();          int len = str.length();          for (int i = 0; i < length; i++) {              sb.append(str.charAt(                      (int) Math.round(Math.random() * (len-1))));          }          return sb.toString();      }    }    class Passenger {      private String name;      private int age;      public String getName() {          return name;      }      public void setName(String name) {          this.name = name;      }      public int getAge() {          return age;      }      public void setAge(int age) {          this.age = age;      }      public Passenger(){}            //ThreadLocal存储线程变量      public static ThreadLocal<Passenger> thsd = new ThreadLocal<Passenger>();            public static Passenger getInstance() {          //获取当前线程范围内的共享变量实例          Passenger passenger = thsd.get();          //懒汉模式创建实例          if(passenger == null) {              passenger = new Passenger();              thsd.set(passenger);          }          return passenger;      }        }    View Code</code></pre>    <p>运行结果</p>    <pre>  <code class="language-java">  Thread Thread-1 has put data:vwozg 33  Thread Thread-2 has put data:hubdn 30  Thread Thread-0 has put data:mkwrt 35  moduleA get data from Thread-2:hubdn 30  moduleA get data from Thread-0:mkwrt 35  moduleA get data from Thread-1:vwozg 33  moduleB get data from Thread-1:vwozg 33  moduleB get data from Thread-0:mkwrt 35  moduleB get data from Thread-2:hubdn 30    View Code</code></pre>    <p>创建 3个线程,每个线程要保存一个 Passenger 对象,并且通过 ModuleA 、 ModuleB 来访问每个线程对应保存的 Passenger 对象。</p>    <h2>多线程之间共享变量</h2>    <p>上面我们讨论的是多线程之间如何访问自己的变量。那么多线程之间共享变量时如何的呢,看下的例子,线程 1对共享变量进行减一操作,线程 2 对共享变量进行加 2 操作。</p>    <pre>  <code class="language-java">  public class MutilThreadShareVariable {      static volatile int count = 100;      public static void main(String[] args) throws Exception{          final ShareDataDec sdDec = new ShareDataDec();          final ShareDataInc sdInc = new ShareDataInc();          //线程1          new Thread(new Runnable() {              @Override              public void run() {                   for(int i=0;i<5;i++) {                      sdDec.dec();                      try {                          Thread.sleep(1000);                      } catch (InterruptedException e) {                          e.printStackTrace();                      }                  }              }          }).start();          //线程2          new Thread(new Runnable(){              @Override              public void run() {                  for(int i=0;i<5;i++) {                      sdInc.inc();                      try {                          Thread.sleep(1000);                      } catch (InterruptedException e) {                          e.printStackTrace();                      }                  }              }          }).start();;      }            static class ShareDataDec {          public synchronized void dec() {              count --;              System.out.println("Thread " + Thread.currentThread().getName()                       + " dec 1 from count,count remain " + count);          }      }            static class ShareDataInc {          public synchronized void inc() {              count = count + 2;              System.out.println("Thread " + Thread.currentThread().getName()                       + " inc 2 from count,count remain " + count);          }      }  }    View Code  </code></pre>    <p>运行结果</p>    <pre>  <code class="language-java">  Thread Thread-0 dec 1 from count,count remain 99  Thread Thread-1 inc 2 from count,count remain 101  Thread Thread-0 dec 1 from count,count remain 100  Thread Thread-1 inc 2 from count,count remain 102  Thread Thread-0 dec 1 from count,count remain 101  Thread Thread-1 inc 2 from count,count remain 103  Thread Thread-0 dec 1 from count,count remain 102  Thread Thread-1 inc 2 from count,count remain 104  Thread Thread-0 dec 1 from count,count remain 103  Thread Thread-1 inc 2 from count,count remain 105    View Code</code></pre>    <p>线程共享变量,只要对要对共享变量进行修改的代码进行同步即可。</p>    <p> </p>    <p>来自:http://www.cnblogs.com/hjwublog/p/6133278.html</p>    <p> </p>