线程是比进程更小的单位,一个进程可以由很多的线程组成。每个进程都有一段专用的内存区域,而线程间可以共享相同的内存区域(包括代码和数据),并利用这些共享单元来实现数据交换、实时通信和必要的同步操作。
每个java程序都有一个默认的主线程。每个main方法可以启动一个线程,这个线程叫做“主线程”,然后可以通过“主线程”创建新的线程。正常的线程在创建后如果没有外部原因,生命周期不再受创建它的线程关联,也就是说main线程结束不会影响到新建线程的执行。当然守护线程是不同的,它同创建它的线程的声明周期息息相关。
java虚拟机允许应用程序并发地运行多个执行线程。
优先级
每个线程都有优先级,高优先级线程的执行优先于低优先级线程。默认的优先级为5.在处理优先级时要注意,较低优先级的线程很有可能会造成活锁。
一个完整的线程有一下几种状态:新建、运行、(中断/挂起/阻塞)、消亡。
1、新建:
当一个Thread类或其子类对象被声明并创建,新生的线程对象就处于新建状态(此时它已经有了内存空间和其他资源)。
2、运行:
线程已经创建就具备了运行的条件,一旦轮到它来享用CPU资源时,即JVM将CPU使用权切换给该线程时,此线程就可以脱离创建它的主线程独立开始自己的生命周期了。
线程创建后仅仅是占有了内存资源,在JVM管理的线程中还没有这个线程,此线程必须调用start()方法(从父类继承)通知JVM,这样JVM就知道又有一个新的线程排队等候切换。
当JVM将CPU使用权切换给线程时,如果线程是Thread的子类创建的,该类中的run()方法立刻执行(run()方法中规定了该线程的具体使命)。Thread类中的run()方法没有具体内容,程序要在Thread类的子类中重写run()方法覆盖父类该方法。(注意:在线程没有结束run()方法前,不要再调用start方法,否则将发生ILLegalThreadStateException异常)
3、挂起:
线程挂起的原因有一下四种:
(1) JVM将CPU资源从当前线程切换给其他线程,使本线程让出CPU的使用权,并处于挂起状态。
(2) 线程使用CPU资源期间,执行了sleep(int millsecond)方法,使当前线程进入休眠状态。sleep(int millsecond)方法是Thread类中的一个类方法,线程执行该方法就立刻让出CPU使用权,进入挂起状态。经过参数millsecond指定的毫秒数之后,该线程就重新进到线程队列中排队等待CPU资源,然后从中断处继续运行。
(3) 线程使用CPU资源期间,执行了wait()方法,使得当前线程进入等待状态。等待状态的线程不会主动进入线程队列等待CPU资源,必须由其他线程调用notify()方法通知它,才能让该线程从新进入到线程队列中排队等待CPU资源,以便从中断处继续运行。
(4) 线程使用CPU资源期间,执行某个操作进入阻塞状态,如执行读/写操作引起阻塞。进入阻塞状态时线程不能进入线程队列,只有引起阻塞的原因消除时,线程才能进入到线程队列排队等待CPU资源,以便从中断处继续运行。
4、死亡:
死亡状态就是线程释放了实体,即释放了分配给线程对象的内存。线程死亡的原因有两个:
(1) 正常运行的线程完成了它的全部工作,即执行完run()方法中的全部语句,结束了run()方法。
(2) 线程被提前强制性终止,即强制run()方法结束。
实现方式
java有2中实现方式。一种是继承Thread,另外一种是实现Runnable接口。
个人比较倡议尽量使用Runnable接口来实现多线程。首先java不支持同时继承多个类,一旦我们继承了Thread类后,可能导致其他需要的类无法继承实现;其次Runnable接口实现的多个线程可以使用同一个实例,这样线程间可以共享一些内存单元(变量,方法),当然用Thread实现的多线程间可以是可以通过类变量或类方法来实现这种共享的,但是类变量的实现方式在变量的共享范围和生存周期上不太好控制。
线程的常用方法
- start():线程调用该方法将启动线程,从新建态进入就绪队列,一旦享用CPU资源就可以脱离创建它的线程,独立开始自己的生命周期。
- run():Thread类的run()方法与Runnable接口中的run()方法功能和作用相同,都用来定义线程对象被调度后所进行的操作,都是系统自动调用而用户不得引用的方法。run()方法执行完毕,线程就成死亡状态,即线程释放了分配给它的内存(死亡态线程不能再调用start()方法)。在线程没有结束run()方法前,不能让线程再调用start()方法,否则将发生IllegalThreadStateException异常。
- sleep(int millsecond):有时,优先级高的线程需要优先级低的线程做一些工作来配合它,此时为让优先级高的线程让出CPU资源,使得优先级低的线程有机会运行,可以使用sleep(int millsecond)方法。线程在休眠时被打断,JVM就抛出InterruptedException异常。因此,必须在try-catch语句块中调用sleep方法。
- isAlive():当线程调用start()方法并占有CPU资源后该线程的run()方法开始运行,在run()方法没有结束之前调用isAlive()返回true,当线程处于新建态或死亡态时调用isAlive()返回false。
注意:一个已经运行的线程在没有进入死亡态时,不要再给它分配实体,由于线程只能引用最后分配的实体,先前的实体就成为了"垃圾",并且不能被垃圾回收机制收集。 - currentThread():是Thread类的类方法,可以用类名调用,返回当前正在使用CPU资源的线程。
- interrupt():当线程调用sleep()方法处于休眠状态,一个占有CPU资源的线程可以让休眠的线程调用interrupt()方法"吵醒"自己,即导致线程发生IllegalThreadStateException异常,从而结束休眠,重新排队等待CPU资源。
- join():等待该线程结束。 一个线程A在占有CPU资源期间,可以让其他线程调用join()方法和本线程联合,如:
B.join();
此时称A在运行期间联合了B。这时A线程立刻终端执行,一直等到它联合的线程B执行完毕,A线程再重新排队等待CPU资源。但如果A准备联合的线程B已经结束,则B.join()不会产生任何效果。
特殊线程
GUI线程
JVM在运行包含图形界面应用程序时,会自动启动更多线程,其中有两个重要的线程:AWT-EventQueue和AWT-Windows。AWT-EventQueue线程负责处理GUI事件,AWT-Windows线程负责将窗体或组件绘制到桌面。
守护线程
守护线程是当程序中所有用户线程都已结束运行时即使守护线程的run()方法还有需要执行的语句,守护线程也会立刻结束运行。因此守护线程用于做一些不是很严格的工作,当线程随时结束时不会产生什么不良后果。