java多线程-概念&创建启动&中断&守护线程&优先级&线程状态(多线程编程之一)

时间:2023-03-09 16:04:47
java多线程-概念&创建启动&中断&守护线程&优先级&线程状态(多线程编程之一)

今天开始就来总结一下Java多线程的基础知识点,下面是本篇的主要内容(大部分知识点参考java核心技术卷1):

1.什么是线程以及多线程与进程的区别
2.多线程的创建与启动
3.中断线程和守护线程以及线程优先级
4.线程的状态转化关系
1.什么是线程以及多线程与进程的区别
        在现代操作在运行一个程序时,会为其创建一个进程。例如启动一个QQ程序,操作系统就会为其创建一个进程。而操作系统中调度的最小单位元是线程,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。因此我们可以这样理解:
进程:正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
线程:是进程中的单个顺序控制流,是一条执行路径一个进程如果只有一条执行路径,则称为单线程程序。一个进程如果有多条执行路径,则称为多线程程序。
2.多线程的创建与启动
    创建多线程有两种方法,一种是继承Thread类重写run方法,另一种是实现Runnable接口重写run方法。下面我们分别给出代码示例,继承Thread类重写run方法:
  1. package com.zejian.test;
  2. /**
  3. * @author zejian
  4. * @time 2016年3月11日 下午9:57:44
  5. * @decrition 继承Thread实现线程
  6. */
  7. public class ThreadByEx extends Thread{
  8. /**
  9. * 重写run方法
  10. */
  11. @Override
  12. public void run() {
  13. System.out.println("I'm a thread that extends Thread!");
  14. }
  15. }

实现Runnable接口重写run方法:

  1. package com.zejian.test;
  2. /**
  3. * @author zejian
  4. * @time 2016年3月11日 下午10:00:36
  5. * @decrition 实现Runnable接口重写run方法
  6. */
  7. public class ThreadByRunnable implements Runnable{
  8. /**
  9. * 实现run方法
  10. */
  11. @Override
  12. public void run() {
  13. System.out.println("I'm a thread that implements Runnable !");
  14. }
  15. }

怎么启动线程?

  1. package com.zejian.test;
  2. public class MainTest {
  3. public static void main(String[] args) {
  4. //继承Thread启动的方法
  5. ThreadByEx t1=new ThreadByEx();
  6. t1.start();//启动线程
  7. //实现Runnable启动线程的方法
  8. ThreadByRunnable r = new ThreadByRunnable();
  9. Thread t2 =new Thread(r);
  10. t2.start();//启动线程
  11. }
  12. }

运行结果:

  1. I'm a thread that extends Thread!
  2. I'm a thread that implements Runnable !
代码相当简单,不过多解释。这里有点需要注意的是调用start()方法后并不是是立即的执行多线程的代码,而是使该线程变为可运行态,什么时候运行多线程代码是由操作系统决定的。
3.中断线程和守护线程以及线程优先级
什么是中断线程?
我们先来看看中断线程是什么?(该解释来自java核心技术一书,我对其进行稍微简化),interrupt方法对其进行中断操作,当一个线程调用interrupt方法时,线程的中断状态(标识位)将被置位(改变),这是每个线程都具有的boolean标志,每个线程都应该不时的检查这个标志,来判断线程是否被中断。而要判断线程是否被中断,我们可以使用如下代码
  1. Thread.currentThread().isInterrupted()
  1. while(!Thread.currentThread().isInterrupted()){
  2. do something
  3. }

但是如果此时线程处于阻塞状态(sleep或者wait),就无法检查中断状态,此时会抛出如果每次迭代之后都调用sleep方法(或者其他可中断的方法),isInterrupted检测就没必要也没用处了,如果在中断状态被置位时调用sleep方法,它不会休眠反而会清除这一休眠状态并抛出InterruptedException。所以如果在循环中调用sleep,不要去检测中断状态,只需捕获InterruptedException。代码范例如下:

void run(){
  • while(more work to do ){
  • try {
  • Thread.sleep(5000);
  • } catch (InterruptedException e) {
  • //thread was interrupted during sleep
  • e.printStackTrace();
  • }finally{
  • //clean up , if required
  • }
  • }
同时还有点要注意的就是我们在捉中断异常时尽量按如下形式处理,不要留空白什么都不处理!
不妥的处理方式:
  1. void myTask(){
  2. ...
  3. try{
  4. sleep(50)
  5. }catch(InterruptedException e){
  6. ...
  7. }
  8. }

正确的处理方式:

  1. void myTask()throw InterruptedException{
  2. sleep(50)
  3. }

或者

  1. void myTask(){
  2. ...
  3. try{
  4. sleep(50)
  5. }catch(InterruptedException e){
  6. Thread.currentThread().interrupt();
  7. }
  8. }
最后关于中断线程,我们这里给出中断线程的一些主要方法:
void interrupt():向线程发送中断请求,线程的中断状态将会被设置为true,如果当前线程被一个sleep调用阻塞,那么将会抛出interrupedException异常。
static boolean interrupted():测试当前线程(当前正在执行命令的这个线程)是否被中断。注意这是个静态方法,调用这个方法会产生一个副作用那就是它会将当前线程的中断状态重置为false。
boolean isInterrupted():判断线程是否被中断,这个方法的调用不会产生副作用即不改变线程的当前中断状态。
static Thread currentThread() : 返回代表当前执行线程的Thread对象。
什么是守护线程?
首先我们可以通过t.setDaemon(true)的方法将线程转化为守护线程。而守护线程的唯一作用就是为其他线程提供服务。计时线程就是一个典型的例子,它定时地发送“计时器滴答”信号告诉其他线程去执行某项任务。当只剩下守护线程时,虚拟机就退出了,因为如果只剩下守护线程,程序就没有必要执行了。。最后还有一点需要特别注意的是在java虚拟机退出时Daemon线程中的finally代码块并不一定会执行哦,代码示例:
  1. package com.zejian.test;
  2. /**
  3. * @author zejian
  4. * @time 2016年3月12日 上午10:42:19
  5. * @decrition 守护线程代码示例
  6. */
  7. public class Demon {
  8. public static void main(String[] args) {
  9. Thread deamon = new Thread(new DaemonRunner(),"DaemonRunner");
  10. //设置为守护线程
  11. deamon.setDaemon(true);
  12. deamon.start();//启动线程
  13. }
  14. static class DaemonRunner implements Runnable{
  15. @Override
  16. public void run() {
  17. try {
  18. Thread.sleep(500);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }finally{
  22. System.out.println("这里的代码在java虚拟机退出时并不一定会执行哦!");
  23. }
  24. }
  25. }
  26. }
因此在构建Daemon线程时,不能依靠finally代码块中的内容来确保执行关闭或清理资源的逻辑。
什么是线程优先级
        Priority来控制线程优先级尽量不要依赖优先级,如果确实要用,应该避免初学者常犯的一个错误。如果有几个高优先级的线程没有进入非活动状态,低优先级线程可能永远也不能执行。每当调度器决定运行一个新线程时,首先会在具有高优先级的线程中进行选择,尽管这样会使低优先级的线程可能永远不会被执行到。因此我们在设置优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高的优先级,而偏重计算(需要较多CPU时间或者运算)的线程则设置较低的优先级,这样才能确保处理器不会被长久独占。当然还有要注意就是在不同的JVM以及操作系统上线程的规划存在差异,有些操作系统甚至会忽略对线程优先级的设定,如mac
os系统或者Ubuntu系统........
4.线程的状态转化关系
(2). 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
(4). 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

- 同步阻塞(Blocked):运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(5). 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

java多线程-概念&创建启动&中断&守护线程&优先级&线程状态(多线程编程之一)

图中的方法解析如下:
Thread.sleep():在指定时间内让当前正在执行的线程暂停执行,但不会释放"锁标志"。不推荐使用。
Thread.sleep(long):使当前线程进入阻塞状态,在指定时间内不会执行。 
Object.wait()和Object.wait(long):在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的"锁标志",从而使别的线程有机会抢占该锁。 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。 唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常,waite()和notify()必须在synchronized函数或synchronized中进行调用。如果在non-synchronized函数或non-synchronized中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。 
Object.notifyAll():则从对象等待池中唤醒所有等待等待线程
Object.notify():则从对象等待池中唤醒其中一个线程。
Thread.yield()方法 暂停当前正在执行的线程对象,yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,yield()只能使同优先级或更高优先级的线程有执行的机会。 
Thread.Join():把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
好了。本篇线程基础知识介绍到此结束。