• java核心(十二):多线程(第一篇)


    一、多线程的实现方式

    Java多线程实现方式主要有三种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程。

    其中前两种方式线程执行完后都没有返回值,后一种是带返回值的。

      1、第一种实现方式:继承Thread类

    • 继承Java.lang.Thread类,重写run()方法,将多线程代码块写入到run()方法中。
    • 启动线程方式:实例化本类对象,然后调用start()方法。
    /**
     * 继承Thread类,重写run()方法
     */
    class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(this.getName()+" , i = "+i); //getName(),获取当前线程的名称
            }
        }
    }
    public class Test {
        public static void main(String[] args) throws Exception {
            MyThread myThread = new MyThread();
            MyThread myThread1 = new MyThread();
            myThread.start();     //Thread-0
            myThread1.start();    //Thread-1
        }
    }
    //程序运行结果:
    Thread-0 , i = 0
    Thread-1 , i = 0
    Thread-1 , i = 1
    Thread-0 , i = 1
    Thread-0 , i = 2
    Thread-0 , i = 3
    Thread-0 , i = 4
    Thread-1 , i = 2
    Thread-1 , i = 3
    Thread-1 , i = 4

      2、第二种实现方式:实现Runnable接口

    • 实现Java.lang.Runnable接口,并重写run()方法,将多线程代码块写入到run()方法中。
    • 启动线程方式:将本类对象作为参数传入给Thread的构造方法,来实例化Thread对象,然后调用Thread对象的start()方法。
    /**
     * 实现Runnable接口,重写run()方法
     */
    class MyThread implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(this+" , i = "+i); 
            }
        }
    }
    public class Test {
        public static void main(String[] args) throws Exception {
            Runnable myThread = new MyThread();
            Runnable myThread1 = new MyThread();
            new Thread(myThread).start();
            new Thread(myThread1).start();
        }
    }
    //注:Thread类的构造方法,可以接收Runnable实例对象
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

      3、第三种实现方式:实现Callable接口

    • 实现java.util.concurrent.Callable接口,并重写call()方法,将多线程代码块写入到call()方法中。
    • 启动线程方式:(1)创建Callable接口实例;(2)然后,创建FutureTask类的实例,并将Callable接口的实例作为参数传入FutureTask实例中;(3)最后,将FutureTask实例作为参数传入给Thread的构造方法,来实例化Thread对象,并调用Thread对象的start()方法,启动多线程。(4)另外,线程结束后,可以通过FutureTask对象的get()方法,获取方法call()方法的返回值。
    • 注:FutureTask<V>是一个包装器,它通过接受Callable<V>来创建,它同时实现了Future和Runnable接口。
    /**
     * 实现Callable接口,重写call()方法
     */
    class MyThread implements Callable {
        @Override
        public Object call() throws Exception {
            for (int i = 0; i < 5; i++) {
                System.out.println(this+" , i = "+i);
            }
            return this + " 线程结束";
        }
    }
    public class Test {
        public static void main(String[] args) throws Exception {
            //创建Callable实例
            Callable<String> myThread = new MyThread();
            Callable<String> myThread1 = new MyThread();
            //由Callable实例,来创建一个FutureTask<String>实例
            //注释:FutureTask<V>是一个包装器,它通过接受Callable<V>来创建,它同时实现了Future和Runnable接口。
            FutureTask<String> myTask = new FutureTask<>(myThread);
            FutureTask<String> myTask1 = new FutureTask<>(myThread1);
            //创建一个Thread对象,并调用start()方法来启动线程
            new Thread(myTask).start();
            new Thread(myTask1).start();
            //如有必要,等待计算完成,然后检索其结果
            System.out.println(myTask.get());
            System.out.println(myTask1.get());
        }
    }
    //程序运行结果:
    com.study.grammar.MyThread@56c2b49a , i = 0
    com.study.grammar.MyThread@762e2323 , i = 0
    com.study.grammar.MyThread@56c2b49a , i = 1
    com.study.grammar.MyThread@56c2b49a , i = 2
    com.study.grammar.MyThread@762e2323 , i = 1
    com.study.grammar.MyThread@56c2b49a , i = 3
    com.study.grammar.MyThread@762e2323 , i = 2
    com.study.grammar.MyThread@56c2b49a , i = 4
    com.study.grammar.MyThread@762e2323 , i = 3
    com.study.grammar.MyThread@56c2b49a 线程结束
    com.study.grammar.MyThread@762e2323 , i = 4
    com.study.grammar.MyThread@762e2323 线程结束

     二、Thread类启动线程的底层实现

      Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法会调用本类的start0()方法,start0()方法是一个native方法,它将启动一个新线程,并执行run()方法。
    
    
    /**
     * Thread类的start0()方法
    * Java开发里面有一门技术成为JNI技术(Java Native Interface),这门技术的特点是:使用Java来调用本机操作系统提供的函数。但是这样以来就存在一个缺点,即:不能离开特定的操作系统。
    * 如果要想线程能够执行,严格来讲是由操作系统来启动线程,并分配系统资源的。
    * 即:使用Thread类的start0()方法,不仅仅包括启动线程的执行代码,而且还包括调用操作系统函数进行资源的分配。
     */
    private native void start0();
    /**
     * Thread类的start()方法
     */
    public synchronized void start() {
        //此异常属于RuntimeException的子类,属于选择性异常。
        if (threadStatus != 0)    
            throw new IllegalThreadStateException();
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    三、Callable接口、Runnable接口,两者的区别

    • 实现多线程时,Runnable接口的方法是run();Callable接口的方法是call()。
    • 实现Runnable接口,任务没有返回值;实现Callable接口,任务可以设置返回值。
    • Runnable无法抛出经过检查的异常,而Callable可以抛出经过检查的异常。
    @FunctionalInterface
    public interface Callable<V> {
        V call() throws Exception;
    }
    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    Callable接口,返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
    Callable接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable不会返回结果,并且无法抛出经过检查的异常。
    Executors 类包含一些从其他普通形式转换成 Callable 类的实用方法。

    四、使用ExecutorService、Callable、Future实现有返回结果的线程

      ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。
      执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
      注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
      下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:
    import java.util.concurrent.*;
    import java.util.Date;
    import java.util.List;
    import java.util.ArrayList;
    
    /**
     * 通过实现Callable接口,实现有返回值的线程
     */
    class MyCallable implements Callable<Object> {
        private String taskNum;
    
        MyCallable(String taskNum) {
            this.taskNum = taskNum;
        }
    
        /**
         * 重写多线程方法,
         */
        public Object call() throws Exception {
            System.out.println(">>>" + taskNum + "任务启动");
            Date dateTmp1 = new Date();
            Thread.sleep(1000);
            Date dateTmp2 = new Date();
            long time = dateTmp2.getTime() - dateTmp1.getTime();
            System.out.println(">>>" + taskNum + "任务终止");
            return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
        }
    }
    
    public class Test {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            System.out.println("----程序开始运行----");
            int taskSize = 5;
            // 创建一个线程池
            ExecutorService pool = Executors.newFixedThreadPool(taskSize);
            // 创建多个有返回值的任务
            List<Future> list = new ArrayList<Future>();
            for (int i = 0; i < taskSize; i++) {
                Callable c = new MyCallable(i + " ");
                // 执行任务并获取Future对象
                Future f = pool.submit(c);  //ExecutorService.submit(),提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
                list.add(f);
            }
            // 关闭线程池
            pool.shutdown();
    
            // 获取所有并发任务的运行结果
            for (Future f : list) {
                // 从Future对象上获取任务的返回值,并输出到控制台
                System.out.println(">>>" + f.get().toString());
            }
        }
    }
    //程序运行结果
    ----程序开始运行----
    >>>0 任务启动
    >>>1 任务启动
    >>>2 任务启动
    >>>3 任务启动
    >>>4 任务启动
    >>>4 任务终止
    >>>1 任务终止
    >>>0 任务终止
    >>>3 任务终止
    >>>0 任务返回运行结果,当前任务时间【1005毫秒】
    >>>1 任务返回运行结果,当前任务时间【1005毫秒】
    >>>2 任务终止
    >>>2 任务返回运行结果,当前任务时间【1005毫秒】
    >>>3 任务返回运行结果,当前任务时间【1005毫秒】
    >>>4 任务返回运行结果,当前任务时间【1005毫秒】
    代码说明:
    上述代码中Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。
    public static ExecutorService newFixedThreadPool(int nThreads) 
    创建固定数目线程的线程池。
    public static ExecutorService newCachedThreadPool() 
    创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
    public static ExecutorService newSingleThreadExecutor() 
    创建一个单线程化的Executor。
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
    创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
    ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
  • 相关阅读:
    哈希表
    矩阵加速(数列)
    线段树
    python
    vue 中防抖
    Windows版本与Internet Explorer版本对照
    一个怂女婿的成长笔记【二十三】
    一个怂女婿的成长笔记【二十一】
    vue xml数据格式化展示,展示在textarea里可编辑,和高亮处理方法
    substring 截取 第三个字符(/)后的字符串
  • 原文地址:https://www.cnblogs.com/newbie27/p/10551705.html
Copyright © 2020-2023  润新知