标签:java线程
一、线程与进程
谈到线程,那就不得不提进程,很久之前其实并没有线程,只有进程,当一个程序需要运行的时候,必然需要使用系统资源和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()方法
本文出自 “12212886” 博客,请务必保留此出处http://12222886.blog.51cto.com/12212886/1963919
标签:java线程
原文地址:http://12222886.blog.51cto.com/12212886/1963919