美文网首页
1.2 多线程 - 线程的创建

1.2 多线程 - 线程的创建

作者: Hey_Shaw | 来源:发表于2018-06-26 20:48 被阅读8次

    每个线程的作用是完成一定的任务,实际就是执行一段程序流(一段顺序执行的代码)。Java使用线程执行体来代表这段程序流。

    继承 Thread 类创建线程类

    创建并启动多线程的步骤如下:

    • 1、定义 Thread 类的子类,并重写该类的 run() 方法,该 run() 方法的方法体就代表了线程需要完成的任务。因此把 run() 方法称为线程执行体
    • 2、创建 Thread 子类的实例,即创建了线程对象。
    • 3、调用线程对象的 start() 方法来启动该线程。

    代码示例:

    // 通过继承Thread类来创建线程类
    public class FirstThread extends Thread { 
        private int i ;
        // 重写run方法,run方法的方法体就是线程执行体
        public void run() {
            for ( ; i < 100 ; i++ ){
                // 当线程类继承Thread类时,直接使用this即可获取当前线程
                // Thread对象的getName()返回当前该线程的名字
                // 因此可以直接调用getName()方法返回当前线程的名
                System.out.println(getName() +  " " + i);
            }
        }
        public static void main(String[] args){
            for (int i = 0; i < 100;  i++){
                // 调用Thread的currentThread方法获取当前线程
                System.out.println(Thread.currentThread().getName()
                    +  " " + i);
                if (i == 20){
                    // 创建、并启动第一条线程
                    new FirstThread().start();
                    // 创建、并启动第二条线程
                    new FirstThread().start();
                }
            }
        }
    }
    

    Java 程序开始运行后,程序至少会创建一个主线程,主线程的线程执行体不是由 run() 方法确定的,而是由 main() 方法确定的 —— main() 方法的方法体代表主线程的线程执行体。

    • Thread.currentThread():currentThread() 是 Thread 类的静态方法,该方法总是返回当前正在执行的线程对象。
    • getName():该方法是 Thread 类的实例方法,该方法返回调用该方法的线程名字。
    • setName(String name):为线程设置名字,默认情况下:主线程的名字为main,用户启动的多个线程的名字依次为 Thread-0、Thread-1、Thread-n。

    实现 Runnable 接口创建线程类

    创建并启动多线程的步骤如下:

    • 1、定义 Runnable 接口的实现类,并重新该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体。
    • 2、创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
    • 3、调用线程对象的 start() 方法来启动该线程。

    代码示例:

    // 通过实现Runnable接口来创建线程类
    public class SecondThread implements Runnable {
        private int i ;
        // run方法同样是线程执行体
        public void run()   {
            for ( ; i < 100 ; i++ ) {
                // 当线程类实现Runnable接口时,
                // 如果想获取当前线程,只能用Thread.currentThread()方法。
                System.out.println(Thread.currentThread().getName()
                    + "  " + i);
            }
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 100;  i++) {
                System.out.println(Thread.currentThread().getName()
                    + "  " + i);
                if (i == 20) {
                    SecondThread st = new SecondThread();     // ①
                    // 通过new Thread(target , name)方法创建新线程
                    new Thread(st , "新线程1").start();
                    new Thread(st , "新线程2").start();
                }
            }
        }
    }
    

    Runnable 对象仅仅作为 Thread 对象的 target ,Runnable 实现类里包含的 run() 方法仅作为线程体。而实际的线程对象依然是 Thread 实例,只是该 Thread 线程负责执行其target 的 run() 方法。总结来说:FirstThread 直接创建的 Thread 子类即可代表线程对象;SecondThread 创建的 Runnable 对象只能作为线程对象的 target。
    采用 Runnable 接口的方式创建的多个线程可以共享线程类的实例变量。

    使用 Callable 和 Future 创建线程

    Callable 接口提供了一个 call() 方法可以作为线程执行体:

    • call() 方法可以有返回值
    • call() 方法可以声明抛出异常

    Callable 接口是Java5新增的接口,并不是 Runnable 接口的子接口,所以不能直接作为 Thread 的 target。call() 方法并不是直接调用,它是作为线程执行体被调用的。

    Java5 提供了 Futurn 接口来代表 Callable 接口里 call() 的返回值,并为 Future 接口提供一个 FutureTask 实现类,该实现类实现了 Futurn 接口,并实现了 Runable 接口 —— 可以作为 Thread 类的 target 。

    Futurn 接口里定义了如下方法来控制它关联的 Callable 任务

    • boolean cancel(boolean mayInterruptIfRunning):试图取消该 Future 里关联的Callable 任务。
    • V get():返回 Callable 任务里 call() 方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值。
    • V get(long timeout, TimeUnit unit):返回 Callable 任务里 call() 方法的返回值。该方法让程序最多阻塞 timeout 和 unit 指定的时间,如果经过指定时间后 Callable 任务依然没有返回值,将会抛出 TimeoutException 异常。
    • boolean isCancelled():如果在 Callable 任务正常完成前被取消,则返回 true 。
    • boolean isDone():如果 Callable 任务已完成,则返回 true 。

    Callable 接口有泛型限制,Callable 接口里的泛型形参类型与 call() 方法返回值类型相同。

    创建并启动有返回值的多线程的步骤如下:

    • 1、创建 Callable 接口的实现类,并实现 call() 方法将作为线程执行体,且该 call() 方法有返回值,再创建 Callable 实现类的实例。Java8 开始,可以直接使用 Lambda 表达式创建 Callable 对象。
    • 2、使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
    • 3、使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
    • 4、调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

    代码示例:

    public class ThirdThread{
        public static void main(String[] args){
            // 创建Callable对象
            ThirdThread rt = new ThirdThread();
            // 先使用Lambda表达式创建Callable<Integer>对象
            // 使用FutureTask来包装Callable对象
            FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
                int i = 0;
                for ( ; i < 100 ; i++ ){
                    System.out.println(Thread.currentThread().getName()
                        + " 的循环变量i的值:" + i);
                }
                // call()方法可以有返回值
                return i;
            });
            for (int i = 0 ; i < 100 ; i++){
                System.out.println(Thread.currentThread().getName()
                    + " 的循环变量i的值:" + i);
                if (i == 20){
                    // 实质还是以Callable对象来创建、并启动线程
                    new Thread(task , "有返回值的线程").start();
                }
            }
            try{
                // 获取线程返回值
                System.out.println("子线程的返回值:" + task.get());
            }
            catch (Exception ex){
                ex.printStackTrace();
            }
        }
    }
    

    创建线程的三种方式对比

    • 采用 Runnable、Callable 接口的方式创建多线程的优缺点:

      • 线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
      • 这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
      • 劣势是,编程稍稍复杂, 如果需要访问当前线程,则必须使用 Thread.currentThread() 方法。采用继承 Thread 类的方式创建多线程的优缺点。
    • 采用继承 Thread 类的方式创建多线程的优缺点:
      - 劣势是:线程类已经继承了 Thread 类,所以不能再继承其他父类。
      - 优势是:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

    相关文章

      网友评论

          本文标题:1.2 多线程 - 线程的创建

          本文链接:https://www.haomeiwen.com/subject/cklmeftx.html