zoukankan      html  css  js  c++  java
  • [02] 线程的创建和常用方法


    1、线程的创建

    线程的创建可以通过两种方式,第一种是 Thread类,第二种是 Runnable接口:
    • 继承 Thread 类,覆盖 run()
    • 实现 Runnable 接口,实现 run()

    然后线程的启用是通过 start() 方法,它会自动调用 run() 方法,如下例:
    //继承Thread
    public class MyThread extends Thread {
        @Override
        public void run() {
            for(int i = 0; i < 100; i++) {
                System.out.println("MyThread:" + i);
            }
        }
    }
    
    //实现Runnable
    public class MyRunnableImpl implements Runnable {
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("MyRunnableImpl:" + i);
            }
        }
    }
    
    //测试类
    public class Test {
        public static void main(String[] args) {
            Thread thread1 = new MyThread();
            Thread thread2 = new Thread(new MyRunnableImpl());
            thread1.start();
            thread2.start();
        }
    }
    
    //输出结果示例
    ...
    MyThread:23
    MyThread:24
    MyThread:25
    MyRunnableImpl:0
    MyThread:26
    MyRunnableImpl:1
    MyThread:27
    MyRunnableImpl:2
    ...

    可以看到,线程的运行是并行的,而不是先执行完整个 thread1 的 start() 再执行 thread2 的 start()

    另外,Runnable 接口的存在主要是为了解决 Java 中不允许多继承的问题,所以我们往往通过实现 Runnable 接口,然后再将其封装到一个 Thread 类中使用(如上例 Thread thread2 = new Thread(new MyRunnableImpl()); )

    事实上,我们也可以通过匿名内部类的方式,快速实现线程,如上可以修改为:
    public class Test {
        public static void main(String[] args) {
            //Thread
            Thread thread1 = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        System.out.println("anonymous thread:" + i);
                    }
                }
            };
            //Runnable
            Thread thread2 = new Thread(new Runnable() {
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        System.out.println("anonymous runnable:" + i);
                    }
                }
            });
    
            thread1.start();
            thread2.start();
        }
    }


    2、线程的常用方法

    2.1 线程的生命周期

    要说到线程的常用方法,首先就要先了解一个线程的生命周期,如下图:
    • 新建状态:使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程

    • 就绪状态:当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中(排队状态),要等待JVM里线程调度器的调度

    • 运行状态:如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态

    • 阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。阻塞又可以分为三种:
      • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态
      • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)
      • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态

    • 死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态

    2.2 线程的常用方法

    2.2.1 start、run

    基于线程的生命周期,那么 start() 和 run() 方法想必也就不用多说了:
    • start()  启动线程,进入就绪状态(万事俱备,只欠CPU)
    • run()    运行状态,执行操作

    2.2.2 sleep

    sleep() 用于使线程进入阻塞状态,强制当前正在执行的线程休眠,计时到后返回到就绪状态。它是Thread类的静态方法:
    • Thread.sleep(long millis)    休眠 ${millis} 毫秒
    • Thread.sleep(long millis, int nanos)    休眠 ${millis} 毫秒 + ${nanos} 纳秒

    注意:休眠完成之后进入的是就绪状态,等待进入运行状态,这意味着 "休眠 --> 执行" 的过程会大于 sleep 方法设置的值,因为实际上还要算上就绪状态的排队时间,即真正过程是 "休眠 --> 就绪 --> 执行"

    另外,sleep() 是 Thread 的静态方法,调用的是当前运行的线程,所以要保证某个线程的休眠,应将 sleep() 的调用放在线程的 run() 之中

    2.2.3 yield

    Thread.yield() 的作用是,暂停当前正在执行的线程,并让其回到就绪状态,以允许具有相同优先级的其他线程获得运行机会。但是,实际中无法保证 yield() 达到让步的目的,因为让步的线程在就绪状态仍可能被再次选中执行。

    注意:yield() 只是将线程转到就绪状态,而不会进入阻塞状态。

    2.2.4 join

    join(long millis) 方法的作用是让某个线程 "霸占" 资源一段时间,如两个线程交互运行,如果 thread2.join(5000),那么 thread2 将强制霸占执行 5s,之后其他线程才有使用的机会。

    2.2.5 setPriority

    setPriority(int newPriority) 方法用于设置线程的优先级别,数值越高表示优先级越高。优先级的范围在 1 - 10 之间,默认为5

    每个线程具有各自的优先级,线程的优先级表示该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定优先使哪个线程进入运行状态。但这并不意味着低优先级的线程得不到运行,只是运行的几率比较小,如垃圾回收机制线程的优先级就比较低。另外:
    • 线程优先级具有继承性,如A线程启动B线程,则B线程的优先级和A一样
    • 线程优先级具有随机性,即线程优先级高的不一定每次都先执行

    2.3 守护线程

    多线程分类
    • 用户线程:运行在前台,执行具体的任务,如程序的主线程,用户自己创建的线程
    • 守护线程:运行在后台,为其他前台线程服务(守护)

    它们的区别在于 "前台线程保证完成,守护线程不保证完成":
    • 前台线程完成了之后,虚拟机执行的进程才会结束
    • 进程结束之后(此时前台线程已经结束),也会结束后台线程,但是不论后台线程是否已经完成都会将其结束

    守护线程的应用如:数据库连接池中的检测线程、JVM启动后的检测线程、垃圾回收线程等

    setDaemon(true) 可以将线程设置为守护线程,必须在线程进入就绪状态之前,即必须在 start() 方法调用之前使用。

    3、参考链接:



  • 相关阅读:
    Google 推出开源博客迁移工具
    Google 的盲人科学家 T. V. RAMAN
    Sun收购Qlayer以积极推动云计算业务
    比尔盖茨:云计算再造软件边界
    一些CodeGuru的COM教程 (英文)
    机器人:人工智能与心理学的较量
    100个最古老互联网域名 最久只有23年(附名单)
    spring2 hibernate3 中包冲突问题解决
    spring2.5+struts2+hibernate+mysql
    [转].NET破解体验 ildasm.exe的使用
  • 原文地址:https://www.cnblogs.com/deng-cc/p/9461621.html
Copyright © 2011-2022 走看看