一、线程与进程

谈到线程,那就不得不提进程,很久之前其实并没有线程,只有进程,当一个程序需要运行的时候,必然需要使用系统资源和CPU,

因此进程就担任了对一个应用程序进行资源分配以及CPU调度这两项职责。后来,为了进一步提高并发执行和资源利用的效率,提出了

线程的概念,将进程作了细分,进程将负责资源的分配,线程来负责CPU的调度。因此可以做以总结,进程是进程是可并发执行的程

序在一个数据集上的一次执行过程,它是系统进行资源分配的基本单位, 线程为进程所有,作为调度执行的基本单位,一个进程可以

有一个或多个线程,他们共享所属进程所拥有的资源。

二、Java中的进程与线程

我们知道Java程序的运行必须依托于JVM,每当我们运行一个Java程序,都会启动一个JVM进程,该JVM的生命周期与程序的生命周期

一致,也就是当程序运行结束或遇到异常退出时,JVM进程也就退出了,JVM就一个职责,运行Java程序。

当JVM运行java程序时,他会创建一个主线程,这个主线程执行的入口是main()方法。JVM虚拟机执行代码的任务全部由线程完成,

每个线程具有自己单独的程序计数器和方法调用栈。


  程序计数器:当线程执行下一个方法时,程序计数器指向方法区中下一条要执行的字节码指令。

       方法调用栈:简称方法栈,用来跟踪线程运行中一系列的方法调用过程,栈中的元素成为栈帧。每当线程调用一个方法的时候,

就会向方法栈压入一个新帧。帧用来存储方法的参数、局部变量和运算过程中的临时数据。

       栈帧由以下三个部分组成:

  局部变量区:存放局部变量和方法参数

  操作数栈:是线程的工作区,用来存放运算过程中生成的临时数据。

  栈数据区:为线程执行指令提供相关的信息,包括如何定位到位于堆区和方法区的特定数据,以及如何正常退出方法或者异常终端方法

就以下代码分析java线程的运行过程:

public class Sample{  
		private int a = 0;  
		public int method(){  
					int b;  
			a++;  
			return a;  
		}  
		  
		public static void main(String args[]){  
			Sample s =new Sample();  
				s.method();  
					System.out.println(a);  
		}  
	}


当启动JVM运行上面程序时,JVM首先创建一个主线程,该线程具有自己的程序计数器和方法栈,开始进入main()方法,进入之后他会将

main()方法的栈帧压入方法栈,将方法的局部变量,参数,以及运算过程的临时数据分别存入栈帧中的局部变量区和操作数栈,同理进入method

后会压入method()的栈帧,主线程能根据method()方法的栈帧的栈数据区中的有关信息,正确定位到堆区的Sample对象的实例变量a,并把它的

值加1,method执行完成之后就弹出method()的栈帧继续指向main()方法。

三、Java线程的创建方法(4中)

继承Thread类

实现Runnable接口

线程池

实现Callable接口

Runnable方式的好处(区别):

(1)、将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将任务

封装成对象

(2)、避免了java单继承的局限性

所以创建线程的第二种方式较为常用

四、线程的状态(5种)

1.新建状态(New)

      条件:通过New语句创建

      特点:仅在堆区中被分配了内存

2.就绪状态(Runnable)

      条件:当一个线程对象创建后,其他线程调用它的start()方法

      特点:Java虚拟机会为它创建方法调用栈和程序计数器,该状态线程位于可运行池中, 等待获得CPU的使用权。

3.运行状态(Running)

      条件:Runnable状态的线程抢占到CPU进入Running状态(只有Runnable状态才可转到运行状态)

      特点:独占一个CPU

4.阻塞状态(Blocked)

       阻塞状态具体可分以下三种情况:

Blocked in object’s wait pool  (位于对象等待池中的阻塞状态)

               条件: wait()

        Blocked in object’s lock pool  (位于对象锁池中的阻塞状态)

               条件:等待获取同步锁时

        Otherwise Blocked (其他阻塞状态)

               条件:sleep()  join()  I/O请求(System.out.println()或System.in.read())

5.死亡状态(Dead)

       条件:退出run()方法  (正常执行完退出 或 遇到异常退出)

       特点:不会对其他线程造成影响

5、线程的调度

如果我们对线程的调度不加管理,那多线程的运行时序是无规律可循的,实际上就算我们对线程调度进行管理,它们的运行时序也不能百分之百确定的,比如让一个线程给另外一个线程运行的机会,可采取以下办法:

   (1)调整线程优先级

         Java中提供了10个优先级,取值范围是整数1~10(关于Java优先级与操作系统优先级之间的映射另外深入学习),Therad类中有3个静态常量:

         l MAX_PRIORITY:取值为10,表示最高优先级

         l MIN_PRIORITY:取值为1,表示最低优先级

         l NORM_PRIORITY:取值为5,表示默认的优先级

         而设置优先级的方法是通过Thread类的setPriority(int)方法

         所有处于就绪状态的线程根据优先级存放在可运行池中,优先级低的线程获得较少的运行机会,优先级高的线程获得较多的运行机会。

 

  (2)让Running状态线程调用Thread.sleep()方法

        假如让一个线程调用sleep()方法,你可以给它一个精确的数值1000毫秒,但是从开始执行sleep(1000)方法到该线程再一次进入Running状态之间的时间确并不是精确的1000毫秒,

   因为1000毫秒过后它进入Runnable状态,而从Runnable状态到Running状态的时间是不确定的,这个时间取决于很多因素,比如CPU运算速度,调度算法,当前正在运行的进程或线程等。

  (3)让Running 状态线程调用Thread.yield()方法

        而最能证明线程管理的不确定性就是Thread.yield()方法了,yield翻译过来是“屈服”的意思,实际上我们可以把它理解为假装屈服,因为你稍微慢一点,下一时刻抢占到CPU的还可能是原来的线程。

         sleep() 和 yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。两者区别在于:

1.sleep () 方法会给其它线程运行的机会,而不考虑其他线程的优先级,而yield()方法只会给相同优先级或者更高优先级一个运行的机会。

2.当线程执行sleep()方法后将转到阻塞状态;当线程执行yield()方法后,将转到就绪状态。

3.sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常。所以sleep()语句要放在try-catch中。

4.sleep()方法比yield()具有更好的可移植性,在实际应用中不能只依靠yield()方法来提高程序的并发性能。

  (4)让Running状态线程调用另一个线程的join()方法

        当前运行的线程可以调用另一个线程的join()方法,当前运行的线程将转到阻塞状态,直至另一个线程运行结束,它才会从阻塞状态转到就绪状态,获得运行机会。

五、守护线程

   概念:为其他线程提供服务的线程,也称为守护线程。Java虚拟机的垃圾回收线程就是典型的后台线程,它负责回收其他线程不再使用的内存。

   特点:后台线程与前台线程相伴相随,后台线程可能在前台线程执行完毕之前结束,如果前台线程运行结束后,加入后台线程还在运行,Java虚拟机就会终止后台线程。

这又反应在后台线程的概念上,后台线程是为服务于前台线程而存在的,倘若没有前台线程在执行,后台线程也就没有了存在的必要。

   主线程在默认情况下是前台线程,由前台线程创建的线程在默认情况下也是前台线程;由后台线程创建的线程在默认情况下仍然是后台线程。

   将前台线程设置为后台线程:调用Thread类的setDaemon(true)方法。

   判断线程是否为后台线程:调用Thread的isDaemon()方法