Java线程详解(深度好文)

Java线程:概念与原理

1、进程与线程

        进程是指一个内存中运行的应用程序,每一个进程都有本身独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中能够启动多个线程。好比在Windows系统中,一个运行的exe就是一个进程。前端

        线程是指进程中的一个执行流程,一个进程中能够运行多个线程。好比java.exe进程中能够运行不少线程。线程老是属于某个进程,线程没有本身的虚拟地址空间,与进程内的其余线程一块儿共享分配给该进程的全部资源。java

        “同时”执行是人的感受,在线程之间实际上轮换执行。程序员

        进程在执行过程当中拥有独立的内存单元,进程有独立的地址空间,而多个线程共享内存,从而极大地提升了程序的运行效率。数据库

        线程在执行过程当中与进程仍是有区别的。每一个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。可是线程不可以独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。编程

        进程是具备必定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位安全

        线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程本身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),可是它可与同属一个进程的其余的线程共享进程所拥有的所有资源。多线程

        线程有本身的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程包含如下内容:并发

  •  一个指向当前被执行指令的指令指针;
  • 一个栈;
  • 一个寄存器值的集合,定义了一部分描述正在执行线程的处理器状态的值
  • 一个私有的数据区。

        咱们使用Join()方法挂起当前线程,直到调用Join()方法的线程执行完毕。该方法还存在包含参数的重载版本,其中的参数用于指定等待线程结束的最长时间(即超时)所花费的毫秒数。若是线程中的工做在规定的超时时段内结束,该版本的Join()方法将返回一个布尔量True。编程语言

        简而言之:ide

  • 一个程序至少有一个进程,一个进程至少有一个线程。
  • 线程的划分尺度小于进程,使得多进程程序的并发性高。
  • 另外,进程在执行过程当中拥有独立的内存单元,而多个线程共享内存,从而极大地提升了程序的运行效率。
  • 线程在执行过程当中与进程仍是有区别的。每一个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。可是线程不可以独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  • 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分能够同时执行。但操做系统并无将多个线程看作多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

         在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收集线程。由于每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每个JVM实际上就是在操做系统中启动了一个进程。

2、Java中的线程

        在Java中,“线程”指两件不一样的事情:

        一、java.lang.Thread类的一个实例;

        二、线程的执行。

        在 Java程序中,有两种方法建立线程:

        一是对 Thread 类进行派生并覆盖 run方法;

        二是经过实现Runnable接口建立。

        使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。

        一个Thread类实例只是一个对象,像Java中的任何其余对象同样,具备变量和方法,生死于堆上。

        Java中,每一个线程都有一个调用栈,即便不在程序中建立任何新的线程,线程也在后台运行着。

        一个Java应用老是从main()方法开始运行,main()方法运行在一个线程内,他被称为主线程。

        一旦建立一个新的线程,就产生一个新的调用栈。

        线程整体分两类:用户线程和守候线程。

        当全部用户线程执行完毕的时候,JVM自动关闭。可是守候线程却不独立于JVM,守候线程通常是由操做系统或者用户本身建立的。

Java线程:建立与启动

1、定义线程

        一、扩展java.lang.Thread类。

        此类中有个run()方法,应该注意其用法:public void run()

        若是该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run方法;不然,该方法不执行任何操做并返回。

        Thread的子类应该重写该方法。

二、实现java.lang.Runnable接口。

        void run()

        使用实现接口Runnable的对象建立一个线程时,启动该线程将致使在独立执行的线程中调用对象的run方法。

        方法run的常规协定是,它可能执行任何所需的操做。

2、实例化线程

        一、若是是扩展java.lang.Thread类的线程,则直接new便可。

        二、若是是实现了java.lang.Runnable接口的类,则用Thread的构造方法:

[java]  view plain  copy
  1. Thread(Runnabletarget)  
  2. Thread(Runnabletarget, String name)  
  3. Thread(ThreadGroupgroup, Runnable target)  
  4. Thread(ThreadGroupgroup, Runnable target, String name)  
  5. Thread(ThreadGroupgroup, Runnable target, String name, long stackSize)  

        其中:

        Runnable target:实现了Runnable接口的类的实例。 

  1. Thread类也实现了Runnable接口,所以,从Thread类继承的类的实例也能够做为target传入这个构造方法。
  2. 直接实现Runnable接口类的实例。
  3. 线程池创建多线程。

        String name:线程的名子。这个名子能够在创建Thread实例后经过Thread类的setName方法设置。默认线程名:Thread-N,N是线程创建的顺序,是一个不重复的正整数。

        ThreadGroup group:当前创建的线程所属的线程组。若是不指定线程组,全部的线程都被加到一个默认的线程组中。

        long stackSize:线程栈的大小,这个值通常是CPU页面的整数倍。如x86的页面大小是4KB.在x86平台下,默认的线程栈大小是12KB。

3、启动线程

        在线程的Thread对象上调用start()方法,而不是run()或者别的方法。

        在调用start()方法以前:线程处于新状态中,新状态指有一个Thread对象,但尚未一个真正的线程。

        在调用start()方法以后:发生了一系列复杂的事情——

        启动新的执行线程(具备新的调用栈);

        该线程重新状态转移到可运行状态;

        当该线程得到机会执行时,其目标run()方法将运行。

        注意:对Java来讲,run()方法没有任何特别之处。像main()方法同样,它只是新线程知道调用的方法名称(和签名)。所以,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。

4、例子

        一、实现Runnable接口的多线程例子

[java]  view plain  copy
  1. /** 
  2.  * 实现Runnable接口的类 
  3.  */  
  4. public class RunnableImpl implements Runnable{  
  5.     private Stringname;  
  6.     public RunnableImpl(String name) {  
  7.        this.name = name;  
  8.     }  
  9.     @Override  
  10.     public void run() {  
  11.        for (int i = 0; i < 5; i++) {  
  12.            for(long k=0;k<100000000;k++);  
  13.            System.out.println(name+":"+i);  
  14.        }       
  15.     }  
  16. }  
  17.    
  18. /** 
  19.  * 测试Runnable类实现的多线程程序 
  20.  */  
  21. public class TestRunnable {  
  22.    
  23.     public static void main(String[] args) {  
  24.        RunnableImpl ri1=new RunnableImpl("李白");  
  25.        RunnableImpl ri2=new RunnableImpl("屈原");  
  26.        Thread t1=new Thread(ri1);  
  27.        Thread t2=new Thread(ri2);  
  28.        t1.start();  
  29.        t2.start();  
  30.     }  
  31. }  

        执行结果:

[java]  view plain  copy
  1. 屈原:0  
  2. 李白:0  
  3. 屈原:1  
  4. 李白:1  
  5. 屈原:2  
  6. 李白:2  
  7. 李白:3  
  8. 屈原:3  
  9. 李白:4  
  10. 屈原:4  

        二、扩展Thread类实现的多线程例子

[java]  view plain  copy
  1. /** 
  2.  * 测试扩展Thread类实现的多线程程序 
  3.  */  
  4. public class TestThread extends Thread {  
  5.     public TestThread(String name){  
  6.        super(name);  
  7.     }  
  8.     @Override  
  9.     public void run() {  
  10.        for(int i=0;i<5;i++){  
  11.            for(long k=0;k<100000000;k++);  
  12.            System.out.println(this.getName()+":"+i);  
  13.        }  
  14.     }  
  15.     public static void main(String[] args){  
  16.        Thread t1=new TestThread("李白");  
  17.        Thread t2=new TestThread("屈原");  
  18.        t1.start();  
  19.        t2.start();        
  20.     }  
  21. }  
        执行结果:

[java]  view plain  copy
  1. 屈原:0  
  2. 李白:0  
  3. 屈原:1  
  4. 李白:1  
  5. 屈原:2  
  6. 李白:2  
  7. 屈原:3  
  8. 屈原:4  
  9. 李白:3  
  10. 李白:4  

        对于上面的多线程程序代码来讲,输出的结果是不肯定的。其中的一条语句for(long k=0;k<100000000;k++);是用来模拟一个很是耗时的操做的。

5、一些常见问题

        一、线程的名字,一个运行中的线程老是有名字的,名字有两个来源,一个是虚拟机本身给的名字,一个是你本身的定的名字。在没有指定线程名字的状况下,虚拟机总会为线程指定名字,而且主线程的名字老是mian,非主线程的名字不肯定。

        二、线程均可以设置名字,也能够获取线程的名字,连主线程也不例外。

        三、获取当前线程的对象的方法是:Thread.currentThread();

        四、在上面的代码中,只能保证:每一个线程都将启动,每一个线程都将运行直到完成。一系列线程以某种顺序启动并不意味着将按该顺序执行。对于任何一组启动的线程来讲,调度程序不能保证其执行次序,持续时间也没法保证。

        五、当线程目标run()方法结束时该线程完成。

        六、一旦线程启动,它就永远不能再从新启动。只有一个新的线程能够被启动,而且只能一次。一个可运行的线程或死线程能够被从新启动。

        七、线程的调度是JVM的一部分,在一个CPU的机器上上,实际上一次只能运行一个线程。一次只有一个线程栈执行。JVM线程调度程序决定实际运行哪一个处于可运行状态的线程。

        众多可运行线程中的某一个会被选中作为当前线程。可运行线程被选择运行的顺序是没有保障的。

        八、尽管一般采用队列形式,但这是没有保障的。队列形式是指当一个线程完成“一轮”时,它移到可运行队列的尾部等待,直到它最终排队到该队列的前端为止,它才能被再次选中。事实上,咱们把它称为可运行池而不是一个可运行队列,目的是帮助认识线程并不都是以某种有保障的顺序排列而成一个一个队列的事实。

        九、尽管咱们没有没法控制线程调度程序,但能够经过别的方式来影响线程调度的方式。

Java线程:线程栈模型与线程的变量

        要理解线程调度的原理,以及线程执行过程,必须理解线程栈模型。

        线程栈是指某时刻时内存中线程调度的栈信息,当前调用的方法老是位于栈顶。线程栈的内容是随着程序的运行动态变化的,所以研究线程栈必须选择一个运行的时刻(实际上指代码运行到什么地方)。

        下面经过一个示例性的代码说明线程(调用)栈的变化过程。

 

        这幅图描述在代码执行到两个不一样时刻一、2时候,虚拟机线程调用栈示意图。

        当程序执行到t.start();时候,程序多出一个分支(增长了一个调用栈B),这样,栈A、栈B并行执行。

        从这里就能够看出方法调用和线程启动的区别了。

Java线程:线程状态的转换

1、线程状态

        线程的状态转换是线程控制的基础。线程状态总的能够分为五大状态。用一个图来描述以下:

        一、新状态:线程对象已经建立,尚未在其上调用start()方法。

        二、可运行状态:当线程有资格运行,但调度程序尚未把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行以后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。

        三、运行状态:线程调度程序从可运行池中选择一个线程做为当前线程时线程所处的状态。这也是线程进入运行状态的惟一一种方式。

        四、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,可是当前没有条件运行。换句话说,它是可运行的,可是若是某件事件出现,他可能返回到可运行状态。

        五、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,可是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。若是在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

2、阻止线程执行

        对于线程的阻止,考虑一下三个方面,不考虑IO阻塞的状况:

        睡眠;

        等待;

        由于须要一个对象的锁定而被阻塞。

        一、睡眠

        Thread.sleep(longmillis)和Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。当线程睡眠时,它入睡在某个地方,在苏醒以前不会返回到可运行状态。当睡眠时间到期,则返回到可运行状态。

        线程睡眠的缘由:线程执行太快,或者须要强制进入下一轮,由于Java规范不保证合理的轮换。

        睡眠的实现:调用静态方法。       

[java]  view plain  copy
  1. try {  
  2.            Thread.sleep(123);  
  3.        } catch (InterruptedException e) {  
  4.            e.printStackTrace();   
  5.        }  

        睡眠的位置:为了让其余线程有机会执行,能够将Thread.sleep()的调用放线程run()以内。这样才能保证该线程执行过程当中会睡眠。

        例如,在前面的例子中,将一个耗时的操做改成睡眠,以减慢线程的执行。能够这么写:

[java]  view plain  copy
  1. for(int i=0;i<5;i++){  
  2.            // 很耗时的操做,用来减慢线程的执行  
  3.            //for(longk=0;k<100000000;k++);  
  4.            try {  
  5.                 Thread.sleep(3);  
  6.             } catch (InterruptedException e) {  
  7.                 e.printStackTrace();  
  8.             }  
  9.            System.out.println(this.getName()+":"+i);  
  10.     }  
        执行结果:

[java]  view plain  copy
  1. 李白:0  
  2. 李白:1  
  3. 屈原:0  
  4. 李白:2  
  5. 屈原:1  
  6. 李白:3  
  7. 屈原:2  
  8. 李白:4  
  9. 屈原:3  
  10. 屈原:4  

        这样,线程在每次执行过程当中,总会睡眠3毫秒,睡眠了,其余的线程就有机会执行了。

        注意:

        一、线程睡眠是帮助全部线程得到运行机会的最好方法。

        二、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短期。所以,sleep()方法不能保证该线程睡眠到期后就开始执行。

        三、sleep()是静态方法,只能控制当前正在运行的线程。

        下面给个例子:

[java]  view plain  copy
  1. /** 
  2.  * 一个计数器,计数到100,在每一个数字之间暂停1秒,每隔10个数字输出一个字符串 
  3.  */  
  4. public class CalcThread extends Thread {  
  5.     public void run(){  
  6.        for(int i=0;i<100;i++){  
  7.            if ((i)%10==0) {  
  8.               System.out.println("--------"+i);  
  9.            }  
  10.            System.out.print(i);  
  11.            try {  
  12.               Thread.sleep(1);  
  13.               System.out.print("    线程睡眠1毫秒!\n");  
  14.            } catch (InterruptedException e) {  
  15.               e.printStackTrace();  
  16.            }  
  17.        }  
  18.     }  
  19.    
  20.     public static void main(String[] args) {  
  21.        new CalcThread().start();  
  22.     }  
  23. }  

        执行结果:

[java]  view plain  copy
  1. --------0  
  2. 0    线程睡眠1毫秒!  
  3. 1    线程睡眠1毫秒!  
  4. 2    线程睡眠1毫秒!  
  5. 3    线程睡眠1毫秒!  
  6. 4    线程睡眠1毫秒!  
  7. 5    线程睡眠1毫秒!  
  8. 6    线程睡眠1毫秒!  
  9. 7    线程睡眠1毫秒!  
  10. 8    线程睡眠1毫秒!  
  11. 9    线程睡眠1毫秒!  
  12. --------10  
  13. 10    线程睡眠1毫秒!  
  14. 11    线程睡眠1毫秒!  
  15. 12    线程睡眠1毫秒!  
  16. 13    线程睡眠1毫秒!  
  17. 14    线程睡眠1毫秒!  
  18. 15    线程睡眠1毫秒!  
  19. 16    线程睡眠1毫秒!  
  20. 17    线程睡眠1毫秒!  
  21. 18    线程睡眠1毫秒!  
  22. 19    线程睡眠1毫秒!  
  23. --------20  
  24. 20    线程睡眠1毫秒!  
  25. 21    线程睡眠1毫秒!  
  26. 22    线程睡眠1毫秒!  
  27. 23    线程睡眠1毫秒!  
  28. 24    线程睡眠1毫秒!  
  29. 25    线程睡眠1毫秒!  
  30. 26    线程睡眠1毫秒!  
  31. 27    线程睡眠1毫秒!  
  32. 28    线程睡眠1毫秒!  
  33. 29    线程睡眠1毫秒!  
  34. --------30  
  35. 30    线程睡眠1毫秒!  
  36. 31    线程睡眠1毫秒!  
  37. 32    线程睡眠1毫秒!  
  38. 33    线程睡眠1毫秒!  
  39. 34    线程睡眠1毫秒!  
  40. 35    线程睡眠1毫秒!  
  41. 36    线程睡眠1毫秒!  
  42. 37    线程睡眠1毫秒!  
  43. 38    线程睡眠1毫秒!  
  44. 39    线程睡眠1毫秒!  
  45. --------40  
  46. 40    线程睡眠1毫秒!  
  47. 41    线程睡眠1毫秒!  
  48. 42    线程睡眠1毫秒!  
  49. 43    线程睡眠1毫秒!  
  50. 44    线程睡眠1毫秒!  
  51. 45    线程睡眠1毫秒!  
  52. 46    线程睡眠1毫秒!  
  53. 47    线程睡眠1毫秒!  
  54. 48    线程睡眠1毫秒!  
  55. 49    线程睡眠1毫秒!  
  56. --------50  
  57. 50    线程睡眠1毫秒!  
  58. 51    线程睡眠1毫秒!  
  59. 52    线程睡眠1毫秒!  
  60. 53    线程睡眠1毫秒!  
  61. 54    线程睡眠1毫秒!  
  62. 55    线程睡眠1毫秒!  
  63. 56    线程睡眠1毫秒!  
  64. 57    线程睡眠1毫秒!  
  65. 58    线程睡眠1毫秒!  
  66. 59    线程睡眠1毫秒!  
  67. --------60  
  68. 60    线程睡眠1毫秒!  
  69. 61    线程睡眠1毫秒!  
  70. 62    线程睡眠1毫秒!  
  71. 63    线程睡眠1毫秒!  
  72. 64    线程睡眠1毫秒!  
  73. 65    线程睡眠1毫秒!  
  74. 66    线程睡眠1毫秒!  
  75. 67    线程睡眠1毫秒!  
  76. 68    线程睡眠1毫秒!  
  77. 69    线程睡眠1毫秒!  
  78. --------70  
  79. 70    线程睡眠1毫秒!  
  80. 71    线程睡眠1毫秒!  
  81. 72    线程睡眠1毫秒!  
  82. 73    线程睡眠1毫秒!  
  83. 74    线程睡眠1毫秒!  
  84. 75    线程睡眠1毫秒!  
  85. 76    线程睡眠1毫秒!  
  86. 77    线程睡眠1毫秒!  
  87. 78    线程睡眠1毫秒!  
  88. 79    线程睡眠1毫秒!  
  89. --------80  
  90. 80    线程睡眠1毫秒!  
  91. 81    线程睡眠1毫秒!  
  92. 82    线程睡眠1毫秒!  
  93. 83    线程睡眠1毫秒!  
  94. 84    线程睡眠1毫秒!  
  95. 85    线程睡眠1毫秒!  
  96. 86    线程睡眠1毫秒!  
  97. 87    线程睡眠1毫秒!  
  98. 88    线程睡眠1毫秒!  
  99. 89    线程睡眠1毫秒!  
  100. --------90  
  101. 90    线程睡眠1毫秒!  
  102. 91    线程睡眠1毫秒!  
  103. 92    线程睡眠1毫秒!  
  104. 93    线程睡眠1毫秒!  
  105. 94    线程睡眠1毫秒!  
  106. 95    线程睡眠1毫秒!  
  107. 96    线程睡眠1毫秒!  
  108. 97    线程睡眠1毫秒!  
  109. 98    线程睡眠1毫秒!  
  110. 99    线程睡眠1毫秒!  

        二、线程的优先级和线程让步yield()

        线程的让步是经过Thread.yield()来实现的。yield()方法的做用是:暂停当前正在执行的线程对象,并执行其余线程。

        要理解yield(),必须了解线程的优先级的概念。线程老是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数状况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数状况。

        注意:当设计多线程应用程序的时候,必定不要依赖于线程的优先级。由于线程调度优先级操做是没有保障的,只能把线程优先级做用做为一种提升程序效率的方法,可是要保证程序不依赖这种操做。

        当线程池中线程都具备相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操做有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每一个线程提供均等的运行机会。

        设置线程的优先级:线程默认的优先级是建立它的执行线程的优先级。能够经过setPriority(int newPriority)更改线程的优先级。例如:

[java]  view plain  copy
  1. Thread t = new MyThread();  
  2. t.setPriority(8);  
  3. t.start();  

        线程优先级为1~10之间的正整数,JVM从不会改变一个线程的优先级。然而,1~10之间的值是没有保证的。一些JVM可能不能识别10个不一样的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。

        线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:

[java]  view plain  copy
  1. static intMAX_PRIORITY:线程能够具备的最高优先级。  
  2. static intMIN_PRIORITY:线程能够具备的最低优先级。  
  3. static intNORM_PRIORITY:分配给线程的默认优先级。  

        三、Thread.yield()方法

        Thread.yield()方法做用是:暂停当前正在执行的线程对象,并执行其余线程。

         yield()应该作的是让当前运行线程回到可运行状态,以容许具备相同优先级的其余线程得到运行机会。所以,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。可是,实际中没法保证yield()达到让步目的,由于让步的线程还有可能被线程调度程序再次选中。

        结论:yield()从未致使线程转到等待/睡眠/阻塞状态。在大多数状况下,yield()将致使线程从运行状态转到可运行状态,但有可能没有效果。

        四、join()方法

        Thread的非静态方法join()让一个线程B“加入”到另一个线程A的尾部。在A执行完毕以前,B不能工做。例如:

[java]  view plain  copy
  1. Thread t = new MyThread();  
  2. t.start();  
  3. t.join();  

        另外,join()方法还有带超时限制的重载版本。例如t.join(5000);则让线程等待5000毫秒,若是超过这个时间,则中止等待,变为可运行状态。

线程的加入join()对线程栈致使的结果是线程栈发生了变化,固然这些变化都是瞬时的。下面给示意图:

        小结

        到目前位置,介绍了线程离开运行状态的3种方法:

        一、调用Thread.sleep():使当前线程睡眠至少多少毫秒(尽管它可能在指定的时间以前被中断)。

        二、调用Thread.yield():不能保障太多事情,尽管一般它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行。

        三、调用join()方法:保证当前线程中止执行,直到该线程所加入的线程完成为止。然而,若是它加入的线程没有存活,则当前线程不须要中止。

        除了以上三种方式外,还有下面几种特殊状况可能使线程离开运行状态:

        一、线程的run()方法完成。

        二、在对象上调用wait()方法(不是在线程上调用)。

        三、线程不能在对象上得到锁定,它正试图运行该对象的方法代码。

        四、线程调度程序能够决定将当前运行状态移动到可运行状态,以便让另外一个线程得到运行机会,而不须要任何理由。

Java线程:线程的同步与锁

1、同步问题提出

        线程的同步是为了防止多个线程访问一个数据对象时,对数据形成的破坏。

        例如:两个线程ThreadA、ThreadB都操做同一个对象Foo对象,并修改Foo对象上的数据。

[java]  view plain  copy
  1. public class Foo {  
  2.     private int x = 100;  
  3.     public int getX() {  
  4.         return x;  
  5.     }  
  6.     public int fix(int y) {  
  7.         x = x - y;  
  8.         return x;  
  9.     }  
  10. }   
  11.    
  12. public class FooRunnable implements Runnable {  
  13.     private Foo foo =new Foo();  
  14.    
  15.     public static void main(String[] args) {  
  16.        FooRunnable r = new FooRunnable();  
  17.         Thread ta = new Thread(r,"Thread-A");  
  18.         Thread tb = new Thread(r,"Thread-B");  
  19.         ta.start();  
  20.         tb.start();  
  21.     }  
  22.    
  23.     @Override  
  24.     public void run() {  
  25.        for (int i = 0; i < 3; i++) {  
  26.             this.fix(30);  
  27.             try {  
  28.                 Thread.sleep(1);  
  29.             } catch (InterruptedException e) {  
  30.                 e.printStackTrace();  
  31.             }  
  32.             System.out.println(Thread.currentThread().getName()+ " :当前foo对象的x值= " + foo.getX());  
  33.         }  
  34.     }  
  35.    
  36.     public int fix(int y) {  
  37.        return foo.fix(y);  
  38.     }  
  39. }  

        执行结果:

[java]  view plain  copy
  1. Thread-B :当前foo对象的x值= 40  
  2. Thread-A :当前foo对象的x值= 10  
  3. Thread-B :当前foo对象的x值= -20  
  4. Thread-A :当前foo对象的x值= -50  
  5. Thread-B :当前foo对象的x值= -80  
  6. Thread-A :当前foo对象的x值= -80  

        从结果发现,这样的输出值明显是不合理的,缘由是两个线程不加控制的访问Foo对象并修改其数据所致。

        若是要保持结果的合理性,只须要达到一个目的,就是将对Foo的访问加以限制,每次只能有一个线程在访问。这样就能保证Foo对象中数据的合理性了。

        在具体的Java代码中须要完成如下两个操做:

        把竞争访问的资源类Foo变量x标识为private;

        同步修改变量的代码,使用synchronized关键字同步方法或代码。

2、同步和锁定

        一、锁的原理

        Java中每一个对象都有一个内置锁。

        当程序运行到非静态的synchronized同步方法上时,自动得到与正在执行代码类的当前实例(this实例)有关的锁。得到一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。

        当程序运行到synchronized同步方法或代码块时才该对象锁才起做用。

        一个对象只有一个锁。因此,若是一个线程得到该锁,就没有其余线程能够得到锁,直到第一个线程释放(或返回)锁。这也意味着任何其余线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

        释放锁是指持锁线程退出了synchronized同步方法或代码块。

        关于锁和同步,有一下几个要点:

        1)只能同步方法,而不能同步变量和类;

        2)每一个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪一个对象上同步?

        3)没必要同步类中全部的方法,类能够同时拥有同步和非同步方法。

        4)若是两个线程要执行一个类中的synchronized方法,而且两个线程使用相同的实例来调用方法,那么一次只能有一个线程可以执行方法,另外一个须要等待,直到锁被释放。也就是说:若是一个线程在对象上得到一个锁,就没有任何其余线程能够进入(该对象的)类中的任何一个同步方法。

        5)若是线程拥有同步和非同步方法,则非同步方法能够被多个线程自由访问而不受锁的限制。

        6)线程睡眠时,它所持的任何锁都不会释放。

        7)线程能够得到多个锁。好比,在一个对象的同步方法里面调用另一个对象的同步方法,则获取了两个对象的同步锁。

        8)同步损害并发性,应该尽量缩小同步范围。同步不但能够同步整个方法,还能够同步方法中一部分代码块。

        9)在使用同步代码块时候,应该指定在哪一个对象上同步,也就是说要获取哪一个对象的锁。例如:

[java]  view plain  copy
  1. public int fix(int y) {  
  2.        synchronized (this) {  
  3.            x = x - y;  
  4.        }  
  5.        return x;  
  6.    }  

        固然,同步方法也能够改写为非同步方法,但功能彻底同样的,例如:

[java]  view plain  copy
  1. public synchronized int getX() {  
  2.     return x++;  
  3. }  

        与  

[java]  view plain  copy
  1. public int getX() {  
  2.       synchronized (this) {  
  3.           return x;  
  4.       }  
  5.   }  

        效果是彻底同样的。

3、静态方法同步

        要同步静态方法,须要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)。

        例如:

[java]  view plain  copy
  1. public staticsynchronized int setName(String name){  
  2.       Xxx.name = name;  
  3. }  

        等价于

[java]  view plain  copy
  1. public static intsetName(String name){  
  2.       synchronized(Xxx.class){  
  3.             Xxx.name = name;  
  4.       }  
  5. }  

4、若是线程不能得到锁会怎么样

        若是线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的一种池中,必须在那里等待,直到其锁被释放,该线程再次变为可运行或运行为止。

        当考虑阻塞时,必定要注意哪一个对象正被用于锁定:

        一、调用同一个对象中非静态同步方法的线程将彼此阻塞。若是是不一样对象,则每一个线程有本身的对象的锁,线程间彼此互不干预。

        二、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。

        三、静态同步方法和非静态同步方法将永远不会彼此阻塞,由于静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。

        四、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不一样对象上锁定的线程将永远不会彼此阻塞。

5、什么时候须要同步

        在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。

        对于非静态字段中可更改的数据,一般使用非静态方法访问。

        对于静态字段中可更改的数据,一般使用静态方法访问。

        若是须要在非静态方法中使用静态字段,或者在静态字段中调用非静态方法,问题将变得很是复杂。

6、线程安全类

        当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”。

        即便是线程安全类,也应该特别当心,由于操做的线程之间仍然不必定安全。

        举个形象的例子,好比一个集合是线程安全的,有两个线程在操做同一个集合对象,当第一个线程查询集合非空后,删除集合中全部元素的时候。第二个线程也来执行与第一个线程相同的操做,也许在第一个线程查询后,第二个线程也查询出集合非空,可是当第一个执行清除后,第二个再执行删除显然是不对的,由于此时集合已经为空了。

        举个例子:

[java]  view plain  copy
  1. public class NameList {  
  2.     private List nameList = Collections.synchronizedList(newLinkedList());  
  3.    
  4.     public void add(String name) {  
  5.         nameList.add(name);  
  6.     }  
  7.    
  8.     public String removeFirst() {  
  9.        if (nameList.size()>0) {  
  10.        return (String) nameList.remove(0);  
  11.        } else {  
  12.            return null;  
  13.        }  
  14.     }    
  15. }  
  16.    
  17. public class TestNameList {  
  18.     public static void main(String[] args) {  
  19.         final NameList nl =new NameList();  
  20.          nl.add("苏东坡");  
  21.          class NameDropper extends Thread{  
  22.            @Override  
  23.            public void run() {  
  24.               String name = nl.removeFirst();  
  25.                 System.out.println(name);  
  26.            }          
  27.          }  
  28.          Thread t1=new NameDropper();  
  29.          Thread t2=new NameDropper();  
  30.          t1.start();  
  31.          t2.start();  
  32.     }  
  33. }  

        执行结果:

[java]  view plain  copy
  1. 苏东坡  
  2. null  

        虽然集合对象

[java]  view plain  copy
  1. private List nameList =Collections.synchronizedList(new LinkedList());  

是同步的,可是程序还不是线程安全的。

        出现这种事件的缘由是,上例中一个线程操做列表过程当中没法阻止另一个线程对列表的其余操做。

        解决上面问题的办法是,在操做集合对象的NameList上面作一个同步。改写后的代码以下:

[java]  view plain  copy
  1. public class NameList {  
  2.     private List nameList = Collections.synchronizedList(newLinkedList());  
  3.    
  4.     public synchronized void add(String name) {  
  5.         nameList.add(name);  
  6.     }  
  7.    
  8.     public synchronized StringremoveFirst() {  
  9.        if (nameList.size()>0) {  
  10.         return (String) nameList.remove(0);  
  11.        } else {  
  12.            return null;  
  13.        }  
  14.     }    
  15. }  

        这样,当一个线程访问其中一个同步方法时,其余线程只有等待。

7、线程死锁

        死锁对Java程序来讲,是很复杂的,也很难发现问题。当两个线程被阻塞,每一个线程在等待另外一个线程时就发生死锁。

        仍是看一个比较直观的死锁例子:

[java]  view plain  copy
  1. public class Deadlock {  
  2.     private static class Resource{  
  3.        public int value;  
  4.     }  
  5.     private Resource resourceA=new Resource();  
  6.     private Resource resourceB=new Resource();  
  7.     public int read(){  
  8.        synchronized (resourceA) {  
  9.            synchronized (resourceB) {  
  10.               return resourceB.value+resourceA.value;  
  11.            }  
  12.        }  
  13.     }  
  14.     public void write(int a,int b){  
  15.        synchronized(resourceB){  
  16.            synchronized (resourceA) {  
  17.               resourceA.value=a;  
  18.               resourceB.value=b;  
  19.            }  
  20.        }  
  21.     }  
  22. }  

        假设read()方法由一个线程启动,write()方法由另一个线程启动。读线程将拥有resourceA锁,写线程将拥有resourceB锁,二者都坚持等待的话就出现死锁。

        实际上,上面这个例子发生死锁的几率很小。由于在代码内的某个点,CPU必须从读线程切换到写线程,因此,死锁基本上不能发生。

        可是,不管代码中发生死锁的几率有多小,一旦发生死锁,程序就死掉。有一些设计方法能帮助避免死锁,包括始终按照预约义的顺序获取锁这一策略。已经超出SCJP的考试范围。

8、线程同步小结

        一、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。

        二、线程同步方法是经过锁来实现,每一个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其余访问该对象的线程就没法再访问该对象的其余同步方法。

        三、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程得到锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。

        四、对于同步,要时刻清醒在哪一个对象上同步,这是关键。

        五、编写线程安全的类,须要时刻注意对多个线程竞争访问资源的逻辑和安全作出正确的判断,对“原子”操做作出分析,并保证原子操做期间别的线程没法访问竞争资源。

        六、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。

        七、死锁是线程间相互等待锁锁形成的,在实际中发生的几率很是的小。真让你写个死锁程序,不必定好使,呵呵。可是,一旦程序发生死锁,程序将死掉。

Java线程:线程的交互

        线程交互是比较复杂的问题,SCJP要求不很基础:给定一个场景,编写代码来恰当使用等待、通知和通知全部线程。

1、线程交互的基础知识

        SCJP所要求的线程交互知识点须要从java.lang.Object的类的三个方法来学习:

[java]  view plain  copy
  1. void notify()——唤醒在此对象监视器上等待的单个线程。  
  2. void notifyAll()——唤醒在此对象监视器上等待的全部线程。  
  3. void wait()——致使当前的线程等待,直到其余线程调用此对象的 notify()方法或 notifyAll()方法。  

        固然,wait()还有另外两个重载方法:

[java]  view plain  copy
  1. void wait(longtimeout)——致使当前的线程等待,直到其余线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。  
  2. void wait(longtimeout, int nanos)——致使当前的线程等待,直到其余线程调用此对象的 notify()方法或 notifyAll()方法,或者其余某个线程中断当前线程,或者已超过某个实际时间量。  

        以上这些方法是帮助线程传递线程关心的时间状态。

        关于等待/通知,要记住的关键点是:

        必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。

        wait()、notify()、notifyAll()都是Object的实例方法。与每一个对象具备锁同样,每一个对象能够有一个线程列表,他们等待来自该信号(通知)。线程经过执行对象上的wait()方法得到这个等待列表。从那时候起,它再也不执行任何其余指令,直到调用对象的notify()方法为止。若是多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。若是没有线程等待,则不采起任何特殊操做。

        下面看个例子就明白了:

[java]  view plain  copy
  1. /** 
  2.  * 计算输出其余线程锁计算的数据 
  3.  */  
  4. public class ThreadA {  
  5.     public static void main(String[] args) {  
  6.        ThreadB b=new ThreadB();  
  7.        //启动计算线程  
  8.        b.start();  
  9.        //线程A拥有b对象上的锁。线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者  
  10.        synchronized (b) {  
  11.            try {  
  12.               System.out.println("等待对象b完成计算......");  
  13.               b.wait();  
  14.            } catch (InterruptedException e) {  
  15.               e.printStackTrace();  
  16.            }  
  17.            System.out.println("b对象计算的总和是:" + b.total);  
  18.        }  
  19.     }  
  20. }  
  21.    
  22. /** 
  23.  * 计算1+2+3+...+100的和 
  24.  */  
  25. public class ThreadB extends Thread {  
  26.     int total;  
  27.     public void run(){  
  28.        synchronized (this) {  
  29.            for (int i=0;i<101;i++){  
  30.               total+=i;  
  31.            }  
  32.            //(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线程A被唤醒  
  33.            notify();  
  34.        }  
  35.     }  
  36. }  

        执行结果:

[java]  view plain  copy
  1. 等待对象b完成计算......  
  2. b对象计算的总和是:5050  

        千万注意:

        当在对象上调用wait()方法时,执行该代码的线程当即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁。若是线程荣然在完成同步代码,则线程在移出以前不会放弃锁。所以,只要调用notify()并不意味着这时该锁变得可用。

2、多个线程在等待一个对象锁时候使用notifyAll()

        在多数状况下,最好通知等待某个对象的全部线程。若是这样作,能够在对象上使用notifyAll()让全部在此对象上等待的线程冲出等待区,返回到可运行状态。

        举个例子:

[java]  view plain  copy
  1. /** 
  2.  * 计算线程 
  3.  */  
  4. public class Calculator extends Thread {  
  5.     int total;  
  6.     @Override  
  7.     public void run() {  
  8.        synchronized (this) {  
  9.            for(int i=0;i<101;i++){  
  10.               total+=i;  
  11.            }  
  12.         }  
  13.        //通知全部在此对象上等待的线程  
  14.        notifyAll();  
  15.     }    
  16. }  
  17.    
  18. /** 
  19.  * 获取计算结果并输出 
  20.  */  
  21. public class ReaderResult extends Thread {  
  22.     Calculator c;  
  23.     public ReaderResult(Calculator c) {  
  24.        this.c = c;  
  25.     }  
  26.     public void run(){  
  27.        synchronized (c) {  
  28.            try {  
  29.               System.out.println(Thread.currentThread() + "等待计算结果......");  
  30.               c.wait();  
  31.            } catch (InterruptedException e) {  
  32.               e.printStackTrace();  
  33.            }  
  34.             System.out.println(Thread.currentThread()+ "计算结果为:" + c.total);  
  35.        }  
  36.     }  
  37.     public static void main(String[] args) {  
  38.        Calculator calculator=new Calculator();  
  39.        //启动三个线程,分别获取计算结果  
  40.        new ReaderResult(calculator).start();  
  41.        new ReaderResult(calculator).start();  
  42.        new ReaderResult(calculator).start();  
  43.        //启动计算线程  
  44.        calculator.start();  
  45.     }  
  46. }  

        执行结果:

[java]  view plain  copy
  1. Thread[Thread-1,5,main]等待计算结果......  
  2. Thread[Thread-2,5,main]等待计算结果......  
  3. Thread[Thread-3,5,main]等待计算结果......  
  4. Exception in thread"Thread-0" java.lang.IllegalMonitorStateException  
  5.     atjava.lang.Object.notifyAll(Native Method)  
  6.     attest.Calculator.run(Calculator.java:15)  
  7. Thread[Thread-3,5,main]计算结果为:5050  
  8. Thread[Thread-2,5,main]计算结果为:5050  
  9. Thread[Thread-1,5,main]计算结果为:5050  

        运行结果代表,程序中有异常,而且屡次运行结果可能有多种输出结果。这就是说明,这个多线程的交互程序还存在问题。到底是出了什么问题,须要深刻的分析和思考,下面将作具体分析。

        实际上,上面这个代码中,咱们指望的是读取结果的线程在计算线程调用notifyAll()以前等待便可。可是,若是计算线程先执行,并在读取结果线程等待以前调用了notify()方法,那么又会发生什么呢?这种状况是可能发生的。由于没法保证线程的不一样部分将按照什么顺序来执行。幸运的是当读取线程运行时,它只能立刻进入等待状态----它没有作任何事情来检查等待的事件是否已经发生。 ----所以,若是计算线程已经调用了notifyAll()方法,那么它就不会再次调用notifyAll(),----而且等待的读取线程将永远保持等待。这固然是开发者所不肯意看到的问题。

        所以,当等待的事件发生时,须要可以检查notifyAll()通知事件是否已经发生。

        一般,解决上面问题的最佳方式是利用某种循环,该循环检查某个条件表达式,只有当正在等待的事情尚未发生的状况下,它才继续等待。

Java线程:线程的调度-休眠

        Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提升程序的执行效率。

        这里要明确的一点,无论程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能作到精准控制。

        线程休眠的目的是使线程让出CPU的最简单的作法之一,线程休眠时候,会将CPU资源交给其余线程,以便能轮换执行,当休眠必定时间后,线程会苏醒,进入准备状态等待执行。

        线程休眠的方法是Thread.sleep(long millis)和Thread.sleep(long millis, int nanos),均为静态方法,那调用sleep休眠的哪一个线程呢?简单说,哪一个线程调用sleep,就休眠哪一个线程。

[java]  view plain  copy
  1. /** 
  2.  * Java线程:线程的调度-休眠 
  3.  */  
  4. public class TestSleep {  
  5.     public static void main(String[] args) {  
  6.        Thread t1=new MyThread1();  
  7.        Thread t2=new Thread(new MyRunnable());  
  8.        t1.start();  
  9.        t2.start();  
  10.     }  
  11. }  
  12. class MyThread1 extends Thread{  
  13.     @Override  
  14.     public void run() {  
  15.        for(int i=0;i<3;i++){  
  16.            System.out.println("线程1第"+i+"次执行!");  
  17.            try {  
  18.               Thread.sleep(50);  
  19.            } catch (InterruptedException e) {  
  20.               e.printStackTrace();  
  21.            }  
  22.        }  
  23.     }    
  24. }  
  25. class MyRunnable implements Runnable{  
  26.     @Override  
  27.     public void run() {       
  28.        for(int i=0;i<3;i++){  
  29.            System.out.println("线程2第"+i+"次执行!");  
  30.            try {  
  31.               Thread.sleep(50);  
  32.            } catch (InterruptedException e) {  
  33.               e.printStackTrace();  
  34.            }  
  35.        }  
  36.     }    
  37. }  

        执行结果:

[java]  view plain  copy
  1. 线程10次执行!  
  2. 线程20次执行!  
  3. 线程21次执行!  
  4. 线程11次执行!  
  5. 线程22次执行!  
  6. 线程12次执行!  

        从上面的结果输出能够看出,没法精准保证线程执行次序。

Java线程:线程的调度-优先级

        与线程休眠相似,线程的优先级仍然没法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的几率较大,优先级低的并不是没机会执行。

        线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认的优先级为5。

        在一个线程中开启另一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。

[java]  view plain  copy
  1. /** 
  2.  * Java线程:线程的调度-优先级 
  3.  */  
  4. public class TestPriority {  
  5.     public static void main(String[] args) {  
  6.        Thread t1=new MyThread1();  
  7.        Thread t2=new Thread(new MyRunnable());  
  8.        t1.setPriority(10);  
  9.        t2.setPriority(1);  
  10.        t1.start();  
  11.        t2.start();  
  12.     }  
  13. }  
  14. class MyThread1 extends Thread{  
  15.     @Override  
  16.     public void run() {  
  17.        for(int i=0;i<10;i++){  
  18.            System.out.println("线程1第"+i+"次执行!");  
  19.            try {  
  20.               Thread.sleep(100);  
  21.            } catch (InterruptedException e) {  
  22.               e.printStackTrace();  
  23.            }  
  24.        }  
  25.     }    
  26. }  
  27. class MyRunnable implements Runnable{  
  28.     @Override  
  29.     public void run() {       
  30.        for(int i=0;i<10;i++){  
  31.            System.out.println("线程2第"+i+"次执行!");  
  32.            try {  
  33.               Thread.sleep(100);  
  34.            } catch (InterruptedException e) {  
  35.               e.printStackTrace();  
  36.            }  
  37.        }  
  38.     }    
  39. }  

        执行结果:

[java]  view plain  copy
  1. 线程10次执行!  
  2. 线程11次执行!  
  3. 线程12次执行!  
  4. 线程20次执行!  
  5. 线程13次执行!  
  6. 线程21次执行!  
  7. 线程14次执行!  
  8. 线程22次执行!  
  9. 线程15次执行!  
  10. 线程23次执行!  
  11. 线程16次执行!  
  12. 线程24次执行!  
  13. 线程17次执行!  
  14. 线程25次执行!  
  15. 线程18次执行!  
  16. 线程26次执行!  
  17. 线程19次执行!  
  18. 线程27次执行!  
  19. 线程28次执行!  
  20. 线程29次执行!  

Java线程:线程的调度-让步

        线程的让步含义就是使当前运行着线程让出CPU资源,可是让给谁不知道,仅仅是让出,线程状态回到可运行状态

        线程的让步使用Thread.yield()方法,yield()为静态方法,功能是暂停当前正在执行的线程对象,并执行其余线程。

[java]  view plain  copy
  1. /** 
  2.  * Java线程:线程的调度-让步 
  3.  */  
  4. public class Test {  
  5.     public static void main(String[] args) {  
  6.        Thread t1=new MyThread1();  
  7.        Thread t2=new Thread(new MyRunnable());  
  8.        t1.start();  
  9.        t2.start();  
  10.     }  
  11. }  
  12. class MyThread1 extends Thread{  
  13.     @Override  
  14.     public void run() {  
  15.        for(int i=0;i<10;i++){  
  16.            System.out.println("线程1第"+i+"次执行!");          
  17.        }  
  18.     }    
  19. }  
  20. class MyRunnable implements Runnable{  
  21.     @Override  
  22.     public void run() {       
  23.        for(int i=0;i<10;i++){  
  24.            System.out.println("线程2第"+i+"次执行!");  
  25.            Thread.yield();  
  26.        }  
  27.     }    
  28. }  

        执行结果:

[java]  view plain  copy
  1. 线程20次执行!  
  2. 线程10次执行!  
  3. 线程11次执行!  
  4. 线程12次执行!  
  5. 线程13次执行!  
  6. 线程14次执行!  
  7. 线程15次执行!  
  8. 线程16次执行!  
  9. 线程17次执行!  
  10. 线程18次执行!  
  11. 线程19次执行!  
  12. 线程21次执行!  
  13. 线程22次执行!  
  14. 线程23次执行!  
  15. 线程24次执行!  
  16. 线程25次执行!  
  17. 线程26次执行!  
  18. 线程27次执行!  
  19. 线程28次执行!  
  20. 线程29次执行!  

Java线程:线程的调度-合并

        线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另外一个线程执行完毕才能执行时能够使用join方法。

        join为非静态方法,定义以下:

[java]  view plain  copy
  1. void join()——等待该线程终止。     
  2. void join(longmillis)——等待该线程终止的时间最长为 millis毫秒。     
  3. void join(longmillis,int nanos)——等待该线程终止的时间最长为 millis毫秒 + nanos 纳秒。  
[java]  view plain  copy
  1. /** 
  2.  * Java线程:线程的调度-合并 
  3.  */  
  4. public class Test {  
  5.     public static void main(String[] args) {  
  6.        Thread t1=new MyThread1();       
  7.        t1.start();  
  8.        for (int i = 0; i < 20; i++) {  
  9.            System.out.println("主线程第" + i +"次执行!");  
  10.            if (i>2) {  
  11.               try {  
  12.                   ///t1线程合并到主线程中,主线程中止执行过程,转而执行t1线程,直到t1执行完毕后继续。  
  13.                   t1.join();  
  14.               } catch (InterruptedException e) {  
  15.                   e.printStackTrace();  
  16.               }  
  17.            }  
  18.        }  
  19.     }  
  20. }  
  21. class MyThread1 extends Thread{  
  22.     @Override  
  23.     public void run() {  
  24.        for(int i=0;i<10;i++){  
  25.            System.out.println("线程1第"+i+"次执行!");          
  26.        }  
  27.     }    
  28. }  

        执行结果:

[java]  view plain  copy
  1. 主线程第0次执行!  
  2. 主线程第1次执行!  
  3. 主线程第2次执行!  
  4. 主线程第3次执行!  
  5. 线程10次执行!  
  6. 线程11次执行!  
  7. 线程12次执行!  
  8. 线程13次执行!  
  9. 线程14次执行!  
  10. 线程15次执行!  
  11. 线程16次执行!  
  12. 线程17次执行!  
  13. 线程18次执行!  
  14. 线程19次执行!  
  15. 主线程第4次执行!  
  16. 主线程第5次执行!  
  17. 主线程第6次执行!  
  18. 主线程第7次执行!  
  19. 主线程第8次执行!  
  20. 主线程第9次执行!  
  21. 主线程第10次执行!  
  22. 主线程第11次执行!  
  23. 主线程第12次执行!  
  24. 主线程第13次执行!  
  25. 主线程第14次执行!  
  26. 主线程第15次执行!  
  27. 主线程第16次执行!  
  28. 主线程第17次执行!  
  29. 主线程第18次执行!  
  30. 主线程第19次执行!  

Java线程:线程的调度-守护线程

        守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则能够将其设置为守护线程。

        守护线程使用的状况较少,但并不是无用,举例来讲,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在作数据库应用时候,使用的数据库链接池,链接池自己也包含着不少后台线程,监控链接个数、超时时间、状态等等。

        setDaemon方法的详细说明:

[java]  view plain  copy
  1. public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。  
        该方法必须在启动线程前调用。
        该方法首先调用该线程的 checkAccess方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
          参数:on - 若是为true,则将该线程标记为守护线程。
          抛出:
        IllegalThreadStateException- 若是该线程处于活动状态。
        SecurityException- 若是当前线程没法修改该线程。
        另请参见:
        isDaemon(),checkAccess()

[java]  view plain  copy
  1. /** 
  2.  * Java线程:线程的调度-守护线程 
  3.  */  
  4. public class Test {  
  5.     public static void main(String[] args) {  
  6.        Thread t1=new MyCommon();  
  7.        Thread t2=new Thread(new MyDaemon());  
  8.        t2.setDaemon(true);//设置为守护线程  
  9.        t2.start();  
  10.        t1.start();        
  11.     }  
  12. }  
  13. class MyCommon extends Thread{  
  14.     @Override  
  15.     public void run() {  
  16.        for(int i=0;i<5;i++){  
  17.            System.out.println("线程1第"+i+"次执行!");  
  18.            try {  
  19.               Thread.sleep(7);  
  20.            } catch (InterruptedException e) {  
  21.               e.printStackTrace();  
  22.            }  
  23.        }  
  24.     }    
  25. }  
  26. class MyDaemon implements Runnable{  
  27.     @Override  
  28.     public void run() {  
  29.        for (long i = 0; i < 9999999L; i++) {  
  30.            System.out.println("后台线程第" + i +"次执行!");  
  31.            try {  
  32.               Thread.sleep(7);  
  33.            } catch (InterruptedException e) {  
  34.               e.printStackTrace();  
  35.            }  
  36.        }  
  37.     }    
  38. }  

        执行结果:

[java]  view plain  copy
  1. 线程10次执行!  
  2. 后台线程第0次执行!  
  3. 后台线程第1次执行!  
  4. 线程11次执行!  
  5. 后台线程第2次执行!  
  6. 线程12次执行!  
  7. 后台线程第3次执行!  
  8. 线程13次执行!  
  9. 后台线程第4次执行!  
  10. 线程14次执行!  
  11. 后台线程第5次执行!  
  12. 后台线程第6次执行!  
  13. 后台线程第7次执行!  
  14. 后台线程第8次执行!  
  15. 后台线程第9次执行!  
  16. 后台线程第10次执行!  

        从上面的执行结果能够看出:

        前台线程是保证执行完毕的,后台线程尚未执行完毕就退出了。

        实际上:JRE判断程序是否执行结束的标准是全部的前台执线程行完毕了,而无论后台线程的状态,所以,在使用后台县城时候必定要注意这个问题。

Java线程:线程的同步-同步方法

        线程的同步是保证多线程安全访问竞争资源的一种手段。

        线程的同步是Java多线程编程的难点,每每开发者搞不清楚什么是竞争资源、何时须要考虑同步,怎么同步等等问题,固然,这些问题没有很明确的答案,但有些原则问题须要考虑,是否有竞争资源被同时改动的问题?

        在本部分以前,请参阅《Java线程:线程的同步与锁》部分,本部分是在此基础上所写的。

        对于同步,在具体的Java代码中须要完成一下两个操做:

        把竞争访问的资源标识为private;

        同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。

        固然这不是惟一控制并发安全的途径。

        synchronized关键字使用说明

        synchronized只能标记非抽象的方法,不能标识成员变量。

        为了演示同步方法的使用,构建了一个信用卡帐户,起初信用额为100w,而后模拟透支、存款等多个操做。显然银行帐户User对象是个竞争资源,而多个并发操做的是帐户方法oper(int x),固然应该在此方法上加上同步,并将帐户的余额设为私有变量,禁止直接访问。

[java]  view plain  copy
  1. /** 
  2.  * Java线程:线程的同步 
  3.  */  
  4. public class Test {  
  5.     public static void main(String[] args) {  
  6.        User u = new User("张三"100);  
  7.        MyThread t1 = new MyThread("线程A", u, 20);  
  8.        MyThread t2 = new MyThread("线程B", u, -60);  
  9.        MyThread t3 = new MyThread("线程C", u, -80);  
  10.        MyThread t4 = new MyThread("线程D", u, -30);  
  11.        MyThread t5 = new MyThread("线程E", u, 32);  
  12.        MyThread t6 = new MyThread("线程F", u, 21);  
  13.        t1.start();  
  14.        t2.start();  
  15.        t3.start();  
  16.        t4.start();  
  17.        t5.start();  
  18.        t6.start();  
  19.     }  
  20. }  
  21.    
  22. class MyThread extends Thread {  
  23.     private User u;  
  24.     private int y = 0;  
  25.    
  26.     MyThread(String name, User u, int y) {  
  27.        super(name);  
  28.        this.u = u;  
  29.        this.y = y;  
  30.     }  
  31.     public void run() {  
  32.        u.oper(y);  
  33.     }  
  34. }  
  35.    
  36. class User {  
  37.     private String code;  
  38.     private int cash;  
  39.     User(String code, int cash) {  
  40.        this.code = code;  
  41.        this.cash = cash;  
  42.     }  
  43.     public String getCode() {  
  44.        return code;  
  45.     }  
  46.     public void setCode(String code) {  
  47.        this.code = code;  
  48.     }  
  49.    
  50.     /** 
  51.      * 业务方法 
  52.      * @param x  添加x万元 
  53.      */  
  54.     public synchronized void oper(int x) {  
  55.        try {  
  56.            Thread.sleep(10L);  
  57.            this.cash += x;  
  58.            System.out.println(Thread.currentThread().getName() + "运行结束,增长“"  
  59.                   + x + "”,当前用户帐户余额为:" + cash);  
  60.            Thread.sleep(10L);  
  61.        } catch (InterruptedException e) {  
  62.            e.printStackTrace();  
  63.        }  
  64.     }  
  65.     @Override  
  66.     public String toString() {  
  67.        return "User{" + "code='" + code + '\'' + ",cash=" + cash + '}';  
  68.     }  
  69. }  

        执行结果:

[java]  view plain  copy
  1. 线程A运行结束,增长“20”,当前用户帐户余额为:120  
  2. 线程F运行结束,增长“21”,当前用户帐户余额为:141  
  3. 线程D运行结束,增长“-30”,当前用户帐户余额为:111  
  4. 线程B运行结束,增长“-60”,当前用户帐户余额为:51  
  5. 线程E运行结束,增长“32”,当前用户帐户余额为:83  
  6. 线程C运行结束,增长“-80”,当前用户帐户余额为:3  

        反面教材,不一样步的状况,也就是去掉oper(int x)方法的synchronized修饰符,而后运行程序,结果以下:

[java]  view plain  copy
  1. 线程F运行结束,增长“21”,当前用户帐户余额为:121  
  2. 线程D运行结束,增长“-30”,当前用户帐户余额为:91  
  3. 线程B运行结束,增长“-60”,当前用户帐户余额为:31  
  4. 线程E运行结束,增长“32”,当前用户帐户余额为:63  
  5. 线程A运行结束,增长“20”,当前用户帐户余额为:3  
  6. 线程C运行结束,增长“-80”,当前用户帐户余额为:-17  
        很显然,上面的结果是错误的,致使错误的缘由是多个线程并发访问了竞争资源u,并对u的属性作了改动。

        可见同步的重要性。

        注意:

        经过前文可知,线程退出同步方法时将释放掉方法所属对象的锁,但还应该注意的是,同步方法中还能够使用特定的方法对线程进行调度。这些方法来自于java.lang.Object类。

 

[java]  view plain  copy
  1. void notify()      
  2.                     唤醒在此对象监视器上等待的单个线程。      
  3. void notifyAll()      
  4.                     唤醒在此对象监视器上等待的全部线程。      
  5. void wait()      
  6.                     致使当前的线程等待,直到其余线程调用此对象的 notify()方法或 notifyAll()方法。      
  7. void wait(long timeout)      
  8.                     致使当前的线程等待,直到其余线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。      
  9. void wait(long timeout,int nanos)      
  10.                     致使当前的线程等待,直到其余线程调用此对象的 notify()方法或 notifyAll()方法,或者其余某个线程中断当前线程,或者已超过某个实际时间量。  

        结合以上方法,处理多线程同步与互斥问题很是重要,著名的生产者-消费者例子就是一个经典的例子,任何语言多线程必学的例子。

Java线程:线程的同步-同步块

        对于同步,除了同步方法外,还能够使用同步代码块,有时候同步代码块会带来比同步方法更好的效果。

        追其同步的根本的目的,是控制竞争资源的正确的访问,所以只要在访问竞争资源的时候保证同一时刻只能一个线程访问便可,所以Java引入了同步代码快的策略,以提升性能。

        在上个例子的基础上,对oper方法作了改动,由同步方法改成同步代码块模式,程序的执行逻辑并无问题。

[java]  view plain  copy
  1. /** 
  2.  * Java线程:线程的同步-同步代码块 
  3.  */  
  4. public class Test {  
  5.     public static void main(String[] args) {  
  6.        User u = new User("张三"100);  
  7.        MyThread t1 = new MyThread("线程A", u, 20);  
  8.        MyThread t2 = new MyThread("线程B", u, -60);  
  9.        MyThread t3 = new MyThread("线程C", u, -80);  
  10.        MyThread t4 = new MyThread("线程D", u, -30);  
  11.        MyThread t5 = new MyThread("线程E", u, 32);  
  12.        MyThread t6 = new MyThread("线程F", u, 21);  
  13.        t1.start();  
  14.        t2.start();  
  15.        t3.start();  
  16.        t4.start();  
  17.        t5.start();  
  18.        t6.start();  
  19.     }  
  20. }  
  21.    
  22. class MyThread extends Thread{  
  23.     private User u;  
  24.     private int y = 0;  
  25.    
  26.     MyThread(String name, User u, int y) {  
  27.        super(name);  
  28.        this.u = u;  
  29.        this.y = y;  
  30.     }  
  31.     public void run() {  
  32.        u.oper(y);  
  33.     }  
  34. }  
  35.    
  36. class User {  
  37.     private String code;  
  38.     private int cash;  
  39.     User(String code, int cash) {  
  40.        this.code = code;  
  41.        this.cash = cash;  
  42.     }  
  43.     public String getCode() {  
  44.        return code;  
  45.     }  
  46.     public void setCode(String code) {  
  47.        this.code = code;  
  48.     }  
  49.    
  50.     /** 
  51.      * 业务方法 
  52.      * @param x  添加x万元 
  53.      */  
  54.     public void oper(int x) {  
  55.        try {  
  56.            Thread.sleep(10L);  
  57.            synchronized (this) {  
  58.               this.cash += x;  
  59.               System.out.println(Thread.currentThread().getName() + "运行结束,增长“"  
  60.                      + x + "”,当前用户帐户余额为:" + cash);  
  61.            }           
  62.            Thread.sleep(10L);  
  63.        } catch (InterruptedException e) {  
  64.            e.printStackTrace();  
  65.        }  
  66.     }  
  67.     @Override  
  68.     public String toString() {  
  69.        return "User{" + "code='" + code + '\'' + ",cash=" + cash + '}';  
  70.     }  
  71. }  

        执行结果:

[java]  view plain  copy
  1. 线程B运行结束,增长“-60”,当前用户帐户余额为:40  
  2. 线程D运行结束,增长“-30”,当前用户帐户余额为:10  
  3. 线程F运行结束,增长“21”,当前用户帐户余额为:31  
  4. 线程E运行结束,增长“32”,当前用户帐户余额为:63  
  5. 线程C运行结束,增长“-80”,当前用户帐户余额为:-17  
  6. 线程A运行结束,增长“20”,当前用户帐户余额为:3  

        注意:

        在使用synchronized关键字时候,应该尽量避免在synchronized方法或synchronized块中使用sleep或者yield方法,由于synchronized程序块占有着对象锁,你休息那么其余的线程只能一边等着你醒来执行完了才能执行。不但严重影响效率,也不合逻辑。

        一样,在同步程序块内调用yeild方法让出CPU资源也没有意义,由于你占用着锁,其余互斥线程仍是没法访问同步程序块。固然与同步程序块无关的线程能够得到更多的执行时间。

Java线程:并发协做-生产者消费者模型

        对于多线程程序来讲,无论任何编程语言,生产者和消费者模型都是最经典的。就像学习每一门编程语言同样,Hello World!都是最经典的例子。

        实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓储,生产者消费者模型就显得没有说服力了。

        对于此模型,应该明确一下几点:

  1. 生产者仅仅在仓储未满时候生产,仓满则中止生产。
  2. 消费者仅仅在仓储有产品时候才能消费,仓空则等待。
  3. 当消费者发现仓储没产品可消费时候会通知生产者生产。
  4. 生产者在生产出可消费产品时候,应该通知等待的消费者去消费。

        此模型将要结合java.lang.Object的wait与notify、notifyAll方法来实现以上的需求。这是很是重要的。

[java]  view plain  copy
  1. /** 
  2.  * Java线程:并发协做-生产者消费者模型 
  3.  */  
  4. public class Test {  
  5.     public static void main(String[] args) {  
  6.        Godown godown=new Godown(30);  
  7.        Consumer c1=new Consumer(50,godown);  
  8.        Consumer c2=new Consumer(20,godown);  
  9.        Consumer c3=new Consumer(30,godown);  
  10.        Producer p1=new Producer(10,godown);  
  11.        Producer p2=new Producer(10,godown);  
  12.        Producer p3=new Producer(10,godown);  
  13.        Producer p4=new Producer(10,godown);  
  14.        Producer p5=new Producer(10,godown);  
  15.        Producer p6=new Producer(10,godown);  
  16.        Producer p7=new Producer(80,godown);  
  17.        c1.start();  
  18.        c2.start();  
  19.        c3.start();  
  20.        p1.start();  
  21.        p2.start();  
  22.        p3.start();  
  23.        p4.start();  
  24.        p5.start();  
  25.        p6.start();  
  26.        p7.start();  
  27.     }  
  28. }  
  29. /** 
  30.  * 仓库 
  31.  */  
  32. class Godown{  
  33.     public static final int max_size=100;//最大库存量  
  34.     public int curnum;//当前库存量  
  35.     Godown() {  
  36.     }  
  37.     Godown(int curnum){  
  38.        this.curnum=curnum;  
  39.     }  
  40.     /** 
  41.      * 生产指定数量的产品 
  42.      */  
  43.     public synchronized void produce(int neednum){  
  44.        //测试是否须要生产  
  45.        while(neednum+curnum>max_size){  
  46.            System.out.println("要生产的产品数量" + neednum +"超过剩余库存量" + (max_size - curnum) +",暂时不能执行生产任务!");  
  47.            try {  
  48.               //当前的生产线程等待  
  49.               wait();  
  50.            } catch (InterruptedException e) {  
  51.               e.printStackTrace();  
  52.            }  
  53.        }  
  54.        //知足生产条件,则进行生产,这里简单的更改当前库存量  
  55.        curnum+=neednum;  
  56.        System.out.println("已经生产了"+neednum+"个产品,现仓储量为"+curnum);  
  57.        //唤醒在此对象监视器上等待的全部线程  
  58.        notifyAll();  
  59.     }  
  60.     /** 
  61.      * 消费指定数量的产品 
  62.      */  
  63.     public synchronized void consume(int neednum){  
  64.        //测试是否能够消费  
  65.        while(curnum<neednum){  
  66.            try {  
  67.               //当前的生产线程等待  
  68.               wait();  
  69.            } catch (InterruptedException e) {  
  70.               e.printStackTrace();  
  71.            }  
  72.        }  
  73.        //知足消费条件,则进行消费,这里简单的更改当前库存  
  74.        curnum-=neednum;  
  75.        System.out.println("已经消费了" + neednum +"个产品,现仓储量为" + curnum);  
  76.        //唤醒在此对象监视器上等待的全部线程  
  77.        notifyAll();  
  78.     }  
  79. }  
  80. //生产者  
  81. class Producer extends Thread{  
  82.     private int neednum;//生产产品的数量  
  83.     private Godown godown;//仓库  
  84.     Producer(int neednum, Godown godown) {  
  85.        this.neednum = neednum;  
  86.        this.godown = godown;  
  87.     }  
  88.     public void run(){  
  89.        //生产指定数量的产品  
  90.        godown.produce(neednum);  
  91.     }  
  92. }  
  93. //消费者  
  94. class Consumer extends Thread{  
  95.     private int neednum;//消费产品的数量  
  96.     private Godown godown;//仓库  
  97.     Consumer(int neednum, Godown godown) {  
  98.        this.neednum = neednum;  
  99.        this.godown = godown;  
  100.     }  
  101.     public void run(){  
  102.        //消费指定数量的产品  
  103.        godown.consume(neednum);  
  104.     }  
  105. }  

        执行结果:

[java]  view plain  copy
  1. 已经消费了20个产品,现仓储量为10  
  2. 已经生产了10个产品,现仓储量为20  
  3. 已经生产了10个产品,现仓储量为30  
  4. 已经生产了10个产品,现仓储量为40  
  5. 要生产的产品数量80超过剩余库存量60,暂时不能执行生产任务!  
  6. 已经消费了30个产品,现仓储量为10  
  7. 已经生产了10个产品,现仓储量为20
相关文章
相关标签/搜索