线程创建与终止

线程创建

Thread类与Runnable接口的关系

  1. public interface Runnable {
  2.   public abstract void run();
  3. }
  4.  
  5. public class Thread implements Runnable {
  6. /* What will be run. */
  7.   private Runnable target;
  8.   ......
  9.   /**
  10.    * Causes this thread to begin execution; the Java Virtual Machine
  11.   * calls the <code>run</code> method of this thread.
  12.   */
  13.   public synchronized void start() {......}
  14.  
  15.   ......
  16. @Override
  17. public void run() {
  18. if (target != null) {
  19. target.run();
  20. }
  21.   }
  22.   ......
  23. }

Thread类与Runnable接口都位于java.lang包中。从上面我们可以看出,Runnable接口中只定义了run()方法,Thread类实现了Runnable 接口并重写了run()方法。当调用Thread 类的start()方法时,实际上Java虚拟机就去调用Thread 类的run()方法,而Thread 类的run()方法中最终调用的是Runnable类型对象的run()方法。

继承Thread并重写run方法

  1. public class ThreadTest1 extends Thread {
  2. @Override
  3. public void run() {
  4. while(true) {
  5. try {
  6. Thread.sleep(1000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. System.out.println("thread 1:" + Thread.currentThread().getName());
  11. }
  12. }
  13.  
  14. public static void main(String[] args) {
  15. ThreadTest1 thread = new ThreadTest1 ();
  16. thread.start();
  17. }//main end
  18. }

可以写成内部类的形式,new Thread(){@Override run(...)}.start();

实现Runnable接口并重写run方法

  1. public class ThreadTest2 implements Runnable {
  2. @Override
  3. public void run() {
  4. while(true) {
  5. try {
  6. Thread.sleep(1000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. System.out.println("thread 3:" + Thread.currentThread().getName());
  11. }
  12. }
  13. public static void main(String[] args) {
  14. ThreadTest2 thread3 = new ThreadTest2();
  15. Thread thread = new Thread(thread3);
  16. thread.start();
  17. }//main end
  18. }

可以写成内部类的形式,new Thread(new Runnable(){@Override run(...)}).start();

线程终止

当调用Thread类的start()方法时,将会创建一个线程,这时刚创建的线程处于就绪状态(可运行状态),并没有运行,处于就绪状态的线程就可以等JVM调度。当JVM调度该线程时,该线程进入运行状态,即执行Thread类的run()方法中的内容。run()方法执行完,线程结束,线程进入死亡状态。这是线程自然终止的过程,我们也可以通过Thread类提供的一些方法来终止线程。

interrupt()\isInterrupted()\interrupted()方法介绍

stop()方法没有做任何的清除操作就粗暴终止线程,释放该线程所持有的对象锁(下文将介绍),受该对象锁保护的其它对象对其他线程可见,因此具有不安全性。

suspend()方法会使目标线程会停下来,但仍然持有在这之前获得的对象锁,对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。

终上所述,不建议使用stop()方法和suspend()方法来终止线程,通常我们通过interrupt()方法来终止处于阻塞状态和运行状态的线程。

需要注意的是,interrupt()方法不会中断一个正在运行的线程,仅仅是将线程的中断标记设为true,当调用了阻塞方法之后,线程会不断监听中断标志,如果为true,则产生一个InterruptedException异常,将InterruptedException放在catch中就能终止线程。

isInterrupted()方法可以返回中断标记,常用循环判断条件。

interrupted()方法测试当前线程是否已经中断,线程的中断标志由该方法清除。interrupted()除了返回中断标记之外,它还会清除中断标记。

interrupt()用法

看下面例子

  1. public class ThreadInterruptedTest extends Thread {
  2. @Override
  3. public void run() {
  4. try {
  5. int i = 0;
  6. while(!isInterrupted()) {
  7. i ++ ;
  8. Thread.sleep(1000);
  9. System.out.println(this.getName() + " is looping,i=" + i);
  10. }
  11. } catch (InterruptedException e) {
  12. System.out.println(this.getName() +
  13. " catch InterruptedException,state:" + this.getState());
  14. e.printStackTrace();
  15. }
  16. }
  17.  
  18. public static void main(String[] args) throws Exception {
  19.  
  20. ThreadInterruptedTest thread = new ThreadInterruptedTest();
  21. System.out.println(thread.getName()
  22. + " state:" + thread.getState());
  23.  
  24. thread.start();
  25. System.out.println(thread.getName()
  26. + " state:" + thread.getState());
  27.  
  28. Thread.sleep(5000);
  29.  
  30. System.out.println("flag: " + thread.isInterrupted());
  31.  
  32. //发出中断指令
  33. thread.interrupt();
  34.  
  35. System.out.println("flag: " + thread.isInterrupted());
  36.  
  37. System.out.println(thread.getName()
  38. + " state:" + thread.getState());
  39.  
  40. System.out.println(thread.interrupted());
  41. }
  42. }

运行结果

  1. Thread-0 state:NEW
  2. Thread-0 state:RUNNABLE
  3. Thread-0 is looping,i=1
  4. Thread-0 is looping,i=2
  5. Thread-0 is looping,i=3
  6. Thread-0 is looping,i=4
  7. flag: false
  8. flag: true
  9. Thread-0 state:TIMED_WAITING
  10. Thread-0 catch InterruptedException,state:RUNNABLE
  11. false
  12. java.lang.InterruptedException: sleep interrupted
  13. at java.lang.Thread.sleep(Native Method)
  14. at com.itpsc.thread.ThreadInterruptedTest.run(ThreadInterruptedTest.java:11)

从运行结果可以看出,调用interrupt() 发出中断指令前,中断标志位false,发出中断指令后中断标志位为true,而调用interrupted()方法后则中断标志被清除。从发出的异常来看,是在一个sleep interrupted,且发出异常后线程被唤醒,以便线程能从异常中正常退出。

线程运行状态图

线程从创建到终止可能会经历各种状态。在java.lang.Thread.State类的源码中,可以看到线程有以下几种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。各种状态的转换如下:

当通过Thread t = new Thread()方式创建线程时,线程处于新建状态;当调用t.start()方法时,线程进入可运行状态(注意,还没有运行);处于可运行状态的线程将在适当的时机被CPU资源调度器调度,进入运行状态,也就是线程执行run()方法中的内容;run()方法执行完或者程序异常退出线程进入终止状态。线程从运行状态也有可能进入阻塞状态,如调用wait()方法后进入等待对象锁(下文将介绍),调用sleep()方法后进行入计时等待。

线程互斥

现在我们已经知道线程的创建与终止了。互斥,是指系统中的某些共享资源,一次只允许一个线程访问,当一个线程正在访问该临界资源时,其它线程必须等待。

对象锁

在java中,每一个对象有且仅有一个锁,锁也称为对象监视器。通过对象的锁,多个线程之间可以实现对某个方法(临界资源)的互斥访问。那么,如何获取对象的锁呢?当我们调用对象的synchronized修饰的方法或者synchronized修饰的代码块时,锁住的是对象实例,就获取了该对象的锁。

全局锁

Java中有实例对象也有类对象,竟然有对象锁,那么久有类锁,也称全局锁。当synchronized修饰静态方法或者静态代码块时,锁住的是该类的Class实例(字节码对象),获取的便是该类的全局锁。看下面获取对象锁实现线程互斥的两种方式。

线程互斥的两种方式

先看下面这个没有实现线程互斥的例子。

  1. public class SynchronizedTest {
  2.  
  3. public static void main(String[] args) {
  4. new SynchronizedTest().init();
  5. }
  6.  
  7. private void init() {
  8. final Outputer output = new Outputer();
  9. //线程1打印"hello,i am thread 1"
  10. new Thread(new Runnable(){
  11. @Override
  12. public void run() {
  13. while(true) {
  14. try{
  15. Thread.sleep(1000);
  16. }catch(InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. output.output("hello,i am thread 1");
  20. }
  21. }
  22. }).start();
  23.  
  24. //线程2打印"hello,i am thread 2"
  25. new Thread(new Runnable(){
  26. @Override
  27. public void run() {
  28. while(true) {
  29. try{
  30. Thread.sleep(1000);
  31. }catch(InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. output.output("hello,i am thread 2");
  35. }
  36. }
  37. }).start();
  38. }
  39.  
  40. class Outputer {
  41. public void output(String name) {
  42. for(int i=0; i<name.length(); i++) {
  43. System.out.print(name.charAt(i));
  44. }
  45. System.out.println();
  46. }
  47. }
  48. }

运行结果

  1. hello,i am thread 1
  2. hello,i am thread 2
  3. hello,i am hellthread 1
  4. o,i am thread 2
  5. hello,i am thread 2
  6. hello,i am thread 1
  7. hello,i am thread 2
  8. hello,i am threadhel 2lo,i am thread
  9. 1

线程1和线程2同时调用output方法进行输出,从运行结果可以看出,线程之间没有执行完各自的输出任务就被交替了运行了。下面通过对象的锁实现线程1和线程2对output方法的互斥访问。

synchronized修饰方法

使用synchronized 对output方法进行修饰,可以让调用者获得锁。synchronized 修饰方法没有显示声明锁的对象,默认是当前方法所在类的对象this。

  1. public synchronized void output(String name) {
  2. for(int i=0; i<name.length(); i++) {
  3. System.out.print(name.charAt(i));
  4. }
  5. System.out.println();
  6. }  

synchronized修饰代码块

使用synchronized 对output方法中的代码块进行修饰,也可以让调用者获得锁。

  1. public void output(String name) {
  2. synchronized(this){
  3. for(int i=0; i<name.length(); i++) {
  4. System.out.print(name.charAt(i));
  5. }
  6. System.out.println();
  7. }
  8. } 

使用synchronized之后,线程1和线程2对output方法实现了互斥访问。

  1. hello,i am thread 1
  2. hello,i am thread 2
  3. hello,i am thread 1
  4. hello,i am thread 2
  5. hello,i am thread 1
  6. hello,i am thread 2
  7. hello,i am thread 1

synchronized用法

先看下面的例子,我们来总结下synchronized的一些常用用法。

  1. public class SynchronizedTest {
  2.  
  3. public static void main(String[] args) {
  4. new SynchronizedTest().init();
  5. }
  6.  
  7. private void init() {
  8. final Outputer output = new Outputer();
  9. //线程1打印"hello,i am thread 1"
  10. new Thread(new Runnable(){
  11. @Override
  12. public void run() {
  13. output.output("hello,i am thread 1");
  14. }
  15. }).start();
  16.  
  17. //线程2打印"hello,i am thread 2"
  18. new Thread(new Runnable(){
  19. @Override
  20. public void run() {
  21. output.output("hello,i am thread 2");
  22. }
  23. }).start();
  24. }
  25.  
  26. static class Outputer {
  27. public synchronized void output(String name) {
  28. for(int i=0; i<5; i++) {
  29. try {
  30. Thread.sleep(1000);
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. System.out.println(name);
  35. }
  36. }
  37.  
  38. public void output2(String name) {
  39. synchronized(this) {
  40. for(int i=0; i<5; i++) {
  41. try {
  42. Thread.sleep(1000);
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. }
  46. System.out.println(name);
  47. }
  48. }
  49. }
  50.  
  51. public void output3(String name) {
  52. for(int i=0; i<5; i++) {
  53. try {
  54. Thread.sleep(1000);
  55. } catch (InterruptedException e) {
  56. e.printStackTrace();
  57. }
  58. System.out.println(name);
  59. }
  60. }
  61.  
  62. public static synchronized void output4(String name) {
  63. for(int i=0; i<5; i++) {
  64. try {
  65. Thread.sleep(1000);
  66. } catch (InterruptedException e) {
  67. e.printStackTrace();
  68. }
  69. System.out.println(name);
  70. }
  71. }
  72.  
  73. public void output5(String name) {
  74. synchronized(Outputer.class) {
  75. for(int i=0; i<5; i++) {
  76. try {
  77. Thread.sleep(1000);
  78. } catch (InterruptedException e) {
  79. e.printStackTrace();
  80. }
  81. System.out.println(name);
  82. }
  83. }
  84. }
  85. }
  86. }

运行结果

  1. hello,i am thread 1
  2. hello,i am thread 1
  3. hello,i am thread 1
  4. hello,i am thread 1
  5. hello,i am thread 1
  6. hello,i am thread 2
  7. hello,i am thread 2
  8. hello,i am thread 2
  9. hello,i am thread 2
  10. hello,i am thread 2

线程1和线程2同时访问output 对象的synchronized 修饰的output 方法,即两个线程竞争的是output 对象的锁,这是同一个锁,所以当线程1在持有锁的时候,线程2必须等待,即下面的用法1。

用法1

当一个线程访问某个对象的synchronized 方法或者synchronized 代码块时,其它线程对该对象的该synchronized 方法或者synchronized 代码块的访问将阻塞。

用法2

当一个线程访问某个对象的synchronized 方法或者synchronized 代码块时,其它线程对该对象的其他synchronized 方法或者synchronized 代码块的访问将阻塞。

修该上面的SynchronizedTest 例子,线程1访问output方法,线程2访问output2 方法,运行结果同上,因为output方法 和output2方法都属于同一个对象output ,因此线程1和线程2竞争的也是同一个锁。

用法3

当一个线程访问某个对象的synchronized 方法或者synchronized 代码块时,其它线程仍然可以对该对象的其他非synchronized 方法或者synchronized 代码块访问。

修该上面的SynchronizedTest 例子,线程1访问output方法,线程2访问output3方法,运行结果是线程1和线程2交替输出。结果显而易见,线程2访问output3方法并不是synchronized 修饰的output 方法或者代码块,线程2并不需要持有锁,因此线程1的运行不会阻塞线程2的运行。

用法4

当synchronized 修饰静态方法时,锁住的是该类的Class实例(字节码对象)。修该上面的SynchronizedTest 例子,线程1访问output4方法,线程2访问output5方法,运行结果同用法1,说明线程1和线程2竞争的是Outputer类的Class实例(字节码对象)的锁。

线程通信

多个线程之间往往需要相互协作来完成某一个任务,synchronized 和对象锁能实现线程互斥,但是不能实现线程通信。

wait()\notify()\notifyAll()介绍

线程之间的通信通过java.lang包中Object类中的wait()方法和notify()、notifyAll()等方法进行。我们知道,Java中每个对象都有一个锁,wait()方法用于等待对象的锁,notify()、notifyAll()方法用于通知其他线程对象锁可以使用。

wait()\notify()\notifyAll()依赖于对象锁,对象锁是对象所持有,Object类是所有java类的父类,这样每一个java类(对象)都有线程通信的基本方法。这就是这些方法定义在Object类中而不定义在Thread类中的原因。

wait()方法的会让当前线程释放对象锁并进入等待对象锁的状态,当前线程是指正在cpu上运行的线程。当前线程调用notify()\notifyAll()后,等待对象锁的线程将被唤醒。

调用wait()方法或者notify()方法的对象必须和对象锁所属的对象是同一个对象,并且必须在synchronized方法或者synchronized代码块中被调用。

yieId()介绍

yieId()的作用是给线程调度器一个提示,告知线程调度器当前线程愿意让出CPU,但是线程调度器可以忽略这个提示。因此,yieId()的作用仅仅是告知线程调度器当前线程愿意让出CPU给其他线程执行(竟然只是愿意,当前线程可以随时反悔,那其他线程也不一定能得到CPU执行),而且不会让当前线程释放对象锁。

yieId()能让当前线程由运行状态进入到就绪状态,从而让其它具有相同优先级的等待线程获取执行权。但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权,也有可能当前线程又进入到运行状态继续运行。

yieId()只建议在测试环境中使用。

wait()和yield()的区别

 

(1)wait()是让线程由运行状态进入到等待(阻塞)状态,而yield()是让线程由运行状态进入到就绪状态。

(2)wait()是让线程释放它所持有对象的锁,而yield()方法不会释放锁。

多线程交替输出及volatile应用

下面的例子是“主线程输出三次接着子线程输出三次”,重复两次。

  1. public class WaitnotifyTest {
  2.  
  3. public static volatile boolean shouldChildren = false;
  4.  
  5. public static void main(String[] args) throws Exception{
  6. final Outputer outputer = new Outputer();
  7.  
  8. //创建子线程
  9. Thread chrild = new Thread(new Runnable(){
  10. @Override
  11. public void run() {
  12. try {
  13. for(int i=0;i<2;i++)
  14. outputer.children();
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. });
  20. chrild.start();
  21. //主线程
  22. for(int i=0;i<2;i++)
  23. outputer.main();
  24. }
  25. }
  26.  
  27. class Outputer {
  28. //子线程循环输出
  29. public synchronized void children() throws Exception{
  30. while(!WaitnotifyTest.shouldChildren) {
  31. System.out.println(Thread.currentThread().getName()
  32. + " thread end loop,go to waitting");
  33. //子线程进入等待状态
  34. this.wait();
  35. }
  36.  
  37. System.out.println(Thread.currentThread().getName()
  38. + " thread start loop");
  39. for(int i=1; i<=3; i++) {
  40. System.out.println("hello,i am chrildren thread,loop:" + i);
  41. }
  42.  
  43. WaitnotifyTest.shouldChildren = false;
  44. //唤醒主线程
  45. this.notify();
  46. }
  47.  
  48. //主线程循环输出
  49. public synchronized void main() throws Exception{
  50. while(WaitnotifyTest.shouldChildren) {
  51. System.out.println(Thread.currentThread().getName()
  52. + " thread end loop,go to waitting");
  53. //主线程进入等待状态
  54. this.wait();
  55. }
  56.  
  57. System.out.println(Thread.currentThread().getName()
  58. + " thread start loop");
  59. for(int i=1; i<=3; i++) {
  60. System.out.println("hello,i am main thread,loop:" + i);
  61. }
  62.  
  63. WaitnotifyTest.shouldChildren = true;
  64. //唤醒子线程
  65. this.notify();
  66. }
  67. }

运行结果

  1. main thread start loop
  2. hello,i am main thread,loop:1
  3. hello,i am main thread,loop:2
  4. hello,i am main thread,loop:3
  5. main thread end loop,go to waitting
  6. Thread-0 thread start loop
  7. hello,i am chrildren thread,loop:1
  8. hello,i am chrildren thread,loop:2
  9. hello,i am chrildren thread,loop:3
  10. Thread-0 thread end loop,go to waitting
  11. main thread start loop
  12. hello,i am main thread,loop:1
  13. hello,i am main thread,loop:2
  14. hello,i am main thread,loop:3
  15. Thread-0 thread start loop
  16. hello,i am chrildren thread,loop:1
  17. hello,i am chrildren thread,loop:2
  18. hello,i am chrildren thread,loop:3

volatile修饰shouldChildren,线程直接读取shouldChildren变量并且不缓存它,修改了shouldChildren立马让其他线程可见,这就确保线程读取到的变量是一致的。

线程本地变量

线程本地变量

线程本地变量,可能称为线程局部变量更容易理解,即为每一个使用该变量的线程都提供一个变量值的副本,相当于将变量的副本绑定到线程中,每一个线程可以独立地修改自己的变量副本,而不会和其它线程的变量副本冲突。在线程消失之后,线程局部变量的所有副本都会被垃圾回收(下面的源码分析中将提到)。

ThreadLocal实现分析

ThreadLocal

 

在java.lang.Thread类中,有一个ThreadLocal.ThreadLocalMap类型的变量threadLocals,这个变量就是用来存储线程局部变量的。

  1. /* ThreadLocal values pertaining to this thread. This map is maintained
  2. * by the ThreadLocal class. */
  3. ThreadLocal.ThreadLocalMap threadLocals = null; 

下面我们重点分析ThreadLocal的内部实现。ThreadLocal也位于java.lang包中。其主要成员有:

  1. public T get() {}
  2. private T setInitialValue() {}
  3. public void set(T value) {}
  4. private void remove(ThreadLocal key) {}
  5. ThreadLocalMap getMap(Thread t){}
  6. void createMap(Thread t, T firstValue) {}
  7. static class ThreadLocalMap {} 

Set

 

我们从set方法开始。Set方法源码如下

  1. /**
  2. * Sets the current thread's copy of this thread-local variable
  3. * to the specified value. Most subclasses will have no need to
  4. * override this method, relying solely on the {@link #initialValue}
  5. * method to set the values of thread-locals.
  6. *
  7. * @param value the value to be stored in the current thread's copy of
  8. * this thread-local.
  9. */
  10. public void set(T value) {
  11. Thread t = Thread.currentThread();
  12. ThreadLocalMap map = getMap(t);
  13. if (map != null)
  14. map.set(this, value);
  15. else
  16. createMap(t, value);
  17. }  

先获取当前的线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。

这个map其实就是存储线程变量的对象threadLocals。ThreadLocalMap是ThreadLocal中的一个内部类,是一个定制的hashmap以便适用于存储线程本地变量。竟然是定制的hashmap,那么就有Entry 和table(hashmap的内部实现参考上一篇:Java基础加强之集合篇(模块记忆、精要分析))。而ThreadLocalMap中的Entry 继承了WeakReference,弱引用是不能保证不被垃圾回收器回收的,这就是前文提到的在线程消失之后,线程局部变量的所有副本都会被垃圾回收。此外,Entry 中使用ThreadLocal作为key,线程局部变量作为value。如果threadLocals不为空,则设值否者调用createMap方法创建threadLocals。注意设值的时候传的是this而不是当前线程t。

  1. /**
  2. * ThreadLocalMap is a customized hash map suitable only for
  3. * maintaining thread local values. No operations are exported
  4. * outside of the ThreadLocal class. The class is package private to
  5. * allow declaration of fields in class Thread. To help deal with
  6. * very large and long-lived usages, the hash table entries use
  7. * WeakReferences for keys. However, since reference queues are not
  8. * used, stale entries are guaranteed to be removed only when
  9. * the table starts running out of space.
  10. */
  11. static class ThreadLocalMap {
  12.  
  13. /**
  14. * The entries in this hash map extend WeakReference, using
  15. * its main ref field as the key (which is always a
  16. * ThreadLocal object). Note that null keys (i.e. entry.get()
  17. * == null) mean that the key is no longer referenced, so the
  18. * entry can be expunged from table. Such entries are referred to
  19. * as "stale entries" in the code that follows.
  20. */
  21. static class Entry extends WeakReference<ThreadLocal> {
  22. /** The value associated with this ThreadLocal. */
  23. Object value;
  24.  
  25. Entry(ThreadLocal k, Object v) {
  26. super(k);
  27. value = v;
  28. }
  29. } 

接下来我们看看createMap方法

  1. /**
  2. * Create the map associated with a ThreadLocal. Overridden in
  3. * InheritableThreadLocal.
  4. *
  5. * @param t the current thread
  6. * @param firstValue value for the initial entry of the map
  7. * @param map the map to store.
  8. */
  9. void createMap(Thread t, T firstValue) {
  10. t.threadLocals = new ThreadLocalMap(this, firstValue);
  11. } 

createMap方法其实就是为当前线程的threadLocals变量分配空间并存储线程的第一个变量。现在我们已经知道线程是如何初始化并设值自己的局部变量了,下面我们看看取值。

Get

  1. /**
  2. * Returns the value in the current thread's copy of this
  3. * thread-local variable. If the variable has no value for the
  4. * current thread, it is first initialized to the value returned
  5. * by an invocation of the {@link #initialValue} method.
  6. *
  7. * @return the current thread's value of this thread-local
  8. */
  9. public T get() {
  10. Thread t = Thread.currentThread();
  11. ThreadLocalMap map = getMap(t);
  12. if (map != null) {
  13. ThreadLocalMap.Entry e = map.getEntry(this);
  14. if (e != null)
  15. return (T)e.value;
  16. }
  17. return setInitialValue();
  18. }  

先获取当前的线程,然后通过getMap(t)方法获取当前线程存变量的对象threadLocals,如果threadLocals不为空则取值并返回(注意传入的key是this对象而不是当前线程t),否则调用setInitialValue方法初始化。setInitialValue和set方法唯一不同的是调用了initialValue进行初始化,也就是在获取变量之前要初始化。

  1. /**
  2. * Variant of set() to establish initialValue. Used instead
  3. * of set() in case user has overridden the set() method.
  4. *
  5. * @return the initial value
  6. */
  7. private T setInitialValue() {
  8. T value = initialValue();
  9. Thread t = Thread.currentThread();
  10. ThreadLocalMap map = getMap(t);
  11. if (map != null)
  12. map.set(this, value);
  13. else
  14. createMap(t, value);
  15. return value;
  16. }  

总的来讲,每创建一个线程(Thread对象),该线程即拥有存储线程本地变量的threadLocals对象,threadLocals对象初始为null,当通过ThreadLocal对象调用set/get方法时,就会对线程的threadLocals对象进行初始化,并且以当前ThreadLocal对象为键值,以ThreadLocal要保存的变量为value,存到threadLocals。看下面的例子。

ThreadLocal应用

  1. public class ThreadLocalShareVariable {
  2.  
  3. public static void main(String[] args) {
  4. //创建3个线程
  5. for(int i=0; i<3;i++) {
  6. //创建线程
  7. new Thread(new Runnable(){
  8. @Override
  9. public void run() {
  10. //线程设置自己的变量
  11. int age = new Random().nextInt(100);
  12. String name = getRandomString(5);
  13. System.out.println("Thread " + Thread.currentThread().getName()
  14. + " has put data:" + name + " " + age);
  15.  
  16. //存储与当前线程有关的变量
  17. Passenger.getInstance().setName(name);
  18. Passenger.getInstance().setAge(age);
  19.  
  20. //线程访问共享变量
  21. new ModuleA().getData();
  22. new ModuleB().getData();
  23. }
  24. }).start();
  25. }
  26. }
  27.  
  28. static class ModuleA {
  29. public void getData(){
  30. //获取与当前线程有关的变量
  31. String name = Passenger.getInstance().getName();
  32. int data = Passenger.getInstance().getAge();
  33. System.out.println("moduleA get data from "
  34. + Thread.currentThread().getName() + ":" + name + " "+ data);
  35. }
  36. }
  37.  
  38. static class ModuleB {
  39. public void getData(){
  40. //获取与当前线程有关的变量
  41. String name = Passenger.getInstance().getName();
  42. int data = Passenger.getInstance().getAge();
  43. System.out.println("moduleB get data from "
  44. + Thread.currentThread().getName() + ":" + name + " "+ data);
  45. }
  46. }
  47.  
  48. /**
  49. * 随机生成字符串
  50. * @param length
  51. * @return
  52. */
  53. public static String getRandomString(int length){
  54. final String str = "abcdefghijklmnopqrstuvwxyz";
  55. StringBuffer sb = new StringBuffer();
  56. int len = str.length();
  57. for (int i = 0; i < length; i++) {
  58. sb.append(str.charAt(
  59. (int) Math.round(Math.random() * (len-1))));
  60. }
  61. return sb.toString();
  62. }
  63.  
  64. }
  65.  
  66. class Passenger {
  67. private String name;
  68. private int age;
  69. public String getName() {
  70. return name;
  71. }
  72. public void setName(String name) {
  73. this.name = name;
  74. }
  75. public int getAge() {
  76. return age;
  77. }
  78. public void setAge(int age) {
  79. this.age = age;
  80. }
  81. public Passenger(){}
  82.  
  83. //ThreadLocal存储线程变量
  84. public static ThreadLocal<Passenger> thsd = new ThreadLocal<Passenger>();
  85.  
  86. public static Passenger getInstance() {
  87. //获取当前线程范围内的共享变量实例
  88. Passenger passenger = thsd.get();
  89. //懒汉模式创建实例
  90. if(passenger == null) {
  91. passenger = new Passenger();
  92. thsd.set(passenger);
  93. }
  94. return passenger;
  95. }
  96.  
  97. }

运行结果

  1. Thread Thread-1 has put data:vwozg 33
  2. Thread Thread-2 has put data:hubdn 30
  3. Thread Thread-0 has put data:mkwrt 35
  4. moduleA get data from Thread-2:hubdn 30
  5. moduleA get data from Thread-0:mkwrt 35
  6. moduleA get data from Thread-1:vwozg 33
  7. moduleB get data from Thread-1:vwozg 33
  8. moduleB get data from Thread-0:mkwrt 35
  9. moduleB get data from Thread-2:hubdn 30

创建3个线程,每个线程要保存一个Passenger 对象,并且通过ModuleA 、ModuleB来访问每个线程对应保存的Passenger 对象。

多线程之间共享变量

上面我们讨论的是多线程之间如何访问自己的变量。那么多线程之间共享变量时如何的呢,看下的例子,线程1对共享变量进行减一操作,线程2对共享变量进行加2操作。

  1. public class MutilThreadShareVariable {
  2. static volatile int count = 100;
  3. public static void main(String[] args) throws Exception{
  4. final ShareDataDec sdDec = new ShareDataDec();
  5. final ShareDataInc sdInc = new ShareDataInc();
  6. //线程1
  7. new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. for(int i=0;i<5;i++) {
  11. sdDec.dec();
  12. try {
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. }).start();
  20. //线程2
  21. new Thread(new Runnable(){
  22. @Override
  23. public void run() {
  24. for(int i=0;i<5;i++) {
  25. sdInc.inc();
  26. try {
  27. Thread.sleep(1000);
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }
  33. }).start();;
  34. }
  35.  
  36. static class ShareDataDec {
  37. public synchronized void dec() {
  38. count --;
  39. System.out.println("Thread " + Thread.currentThread().getName()
  40. + " dec 1 from count,count remain " + count);
  41. }
  42. }
  43.  
  44. static class ShareDataInc {
  45. public synchronized void inc() {
  46. count = count + 2;
  47. System.out.println("Thread " + Thread.currentThread().getName()
  48. + " inc 2 from count,count remain " + count);
  49. }
  50. }
  51. }

运行结果

  1. Thread Thread-0 dec 1 from count,count remain 99
  2. Thread Thread-1 inc 2 from count,count remain 101
  3. Thread Thread-0 dec 1 from count,count remain 100
  4. Thread Thread-1 inc 2 from count,count remain 102
  5. Thread Thread-0 dec 1 from count,count remain 101
  6. Thread Thread-1 inc 2 from count,count remain 103
  7. Thread Thread-0 dec 1 from count,count remain 102
  8. Thread Thread-1 inc 2 from count,count remain 104
  9. Thread Thread-0 dec 1 from count,count remain 103
  10. Thread Thread-1 inc 2 from count,count remain 105

线程共享变量,只要对要对共享变量进行修改的代码进行同步即可。

Java基础之多线程篇(线程创建与终止、互斥、通信、本地变量)的更多相关文章

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

    线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...

  2. java基础(26):Thread、线程创建、线程池

    1. 多线程 1.1 多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念. 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并 ...

  3. Python多线程之线程创建和终止

    python主要是通过thread和threading这两个模块来实现多线程支持. python的thread模块是比較底层的模块,python的threading模块是对thread做了一些封装,能 ...

  4. 黑马程序员——JAVA基础之多线程的线程间通讯等

    ------- android培训.java培训.期待与您交流! ---------- 线程间通讯: 其实就是多个线程在操作同一个资源,但是动作不同. wait(); 在其他线程调用此对象的notif ...

  5. Java基础学习——多线程之线程池

    1.线程池介绍     线程池是一种线程使用模式.线程由于具有空闲(eg:等待返回值)和繁忙这种不同状态,当数量过多时其创建.销毁.调度等都会带来开销.线程池维护了多个线程,当分配可并发执行的任务时, ...

  6. Java基础学习总结(88)——线程创建与终止、互斥、通信、本地变量

    线程创建与终止 线程创建 Thread类与 Runnable 接口的关系 public interface Runnable {         public abstract void run(); ...

  7. Java基础技术多线程与并发面试【笔记】

    Java基础技术多线程与并发 什么是线程死锁? ​死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,我们就可以称 ...

  8. iOS开发多线程篇—线程的状态

    iOS开发多线程篇—线程的状态 一.简单介绍 线程的创建: self.thread=[[NSThread alloc]initWithTarget:self selector:@selector(te ...

  9. iOS开发多线程篇—线程安全

    iOS开发多线程篇—线程安全 一.多线程的安全隐患 资源共享 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源 比如多个线程访问同一个对象.同一个变量.同一个文件 当多个线程访问同一块 ...

随机推荐

  1. WebForm 【Repeater】展示数据

       在 Webform 数据展示中      界面层  : HTLM 业务逻辑层 :只能用 C#  Repeater    重复器  能够用来循环展示数据 具有5种模板  HeaderTemplat ...

  2. Java 多态 ——一个案例 彻底搞懂它

    最近,发现基础真的hin重要.比如,Java中多态的特性,在学习中就是很难懂,比较抽象的概念.学的时候就犯糊涂,但日后会发现,基础在日常工作的理解中占有重要的角色. 下面,我将用一个代码实例,回忆和巩 ...

  3. 【Java并发编程】6、volatile关键字解析&内存模型&并发编程中三概念

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...

  4. matlab中常数下的点是什么意思

    加上点"."后表示两个矩阵或向量对应位置进行运算, 这时候要求进行操作的两个变量必须维数相同(与矩阵乘法对矩阵维数要求不同)

  5. 实现Java Socket 客户端服务端交互实例

    SocketService.java package socket; import java.io.BufferedReader; import java.io.IOException; import ...

  6. sublime3安装ctags追踪插件

    sublime3经常要用到函数追踪插件,怎做的?下面看安装步骤: 1.安装package control 按快捷键 ctrl+shift+p 2.安装搜索 ctags插件 3.下载ctags可执行程序 ...

  7. 【读书笔记】iOS-工作区的使用

    一,打开Xcode--->File-->New-->Workspace--->AllProject. 二,打开桌面上的AllProject--->File--->N ...

  8. 活字格 QQ 群和客户

    上活字格的官网溜达了一圈,有2点收获: 1. 活字格基础教学 QQ 群 这个群居然有1790人!好大的一个群,是不是说明学活字格的人也多呢,我潜水几天看看先 2. 活字格的用户真是各行各业都有,看到他 ...

  9. 监听软件异常崩溃并且保持日志--CrashHandler编写自己的异常捕获类

    平时写代码,我们可能会抛出各种异常,这些异常有些是我们测试过程中发现进行解决的,但是也有一些异常是我们未知的,不论是代码的逻辑问题还是Android本身底层的一些bug,我们都需要及时了解并进行解决. ...

  10. UML类图关系图解

    一.类结构 在类的UML图中,使用长方形描述一个类的主要构成,长方形垂直地分为三层,以此放置类的名称.属性和方法. 其中, 一般类的类名用正常字体粗体表示,如上图:抽象类名用斜体字粗体,如User:接 ...