美文网首页
常用创建线程的两种方式(3)

常用创建线程的两种方式(3)

作者: hxj688699 | 来源:发表于2018-07-18 18:07 被阅读0次

    概要

    线程作为独立调度运行和分派的基本单位,很明显的一个特征便是可运行的。Runnable接口便是对可被调度运行进行了抽象声明。本章主要内容如下:

    • 线程构造函数介绍
    • Thread和Runnable的区别与联系
    • 创建线程示例

    线程构造函数介绍

        /**
         * 无参构造函数,常用
         */
        public Thread() {
            init(null, null, "Thread-" + nextThreadNum(), 0);
        }
    
        /**
         * 需要参数Runnable对象构造函数,常用
         */
        public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
    
        Thread(Runnable target, AccessControlContext acc) {
            init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
        }
    
        public Thread(ThreadGroup group, Runnable target) {
            init(group, target, "Thread-" + nextThreadNum(), 0);
        }
    
        /**
         * 线程名称,常用
         */
        public Thread(String name) {
            init(null, null, name, 0);
        }
    
        public Thread(ThreadGroup group, String name) {
            init(group, null, name, 0);
        }
    
        /**
         * 常用
         */
        public Thread(Runnable target, String name) {
            init(null, target, name, 0);
        }
    

    创建线程的时候常用无参和设置线程名、设置Runnable对象及组合的方式。常见的有Thread()、Thread(Runnable target)、Thread(String name)和Thread(Runnable target, String name),设置线程组的方式比较少见,除非真的有必要进行分组管理,系统启动的时候会设置系统线程组、main线程组。

    线程init初始化方法

    java.lang.Thread

        /**
         * Initializes a Thread.
         *
         * @param g 线程组
         * @param target 可被调度运行对象
         * @param name 线程名
         * @param stackSize 线程堆栈大小,0表示忽略
         * @param acc 线程内部访问控制
         * @param inheritThreadLocals 是否继承线程本地变量
         */
        private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals) {
            if (name == null) {
                throw new NullPointerException("name cannot be null");
            }
            //设置线程名称
            this.name = name;
    
            Thread parent = currentThread();
            //默认情况下jvm不开启安全管理器
            SecurityManager security = System.getSecurityManager();
            if (g == null) {
                /* Determine if it's an applet or not */
    
                /* If there is a security manager, ask the security manager
                   what to do. */
                if (security != null) {
                    g = security.getThreadGroup();
                }
    
                /* If the security doesn't have a strong opinion of the matter
                   use the parent thread group. */
                if (g == null) {
                    //不是通过设置线程组的方式创建线程,默认归属于父线程的线程组
                    g = parent.getThreadGroup();
                }
            }
    
            /* checkAccess regardless of whether or not threadgroup is
               explicitly passed in. */
            g.checkAccess();
    
            /*
             * Do we have the required permissions?
             */
            if (security != null) {
                if (isCCLOverridden(getClass())) {
                    security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
                }
            }
    
            g.addUnstarted();
            //线程组赋值
            this.group = g;
            //是否守护线程继承默认继承自父线程
            this.daemon = parent.isDaemon();
            //线程优先级,也是继承自父线程,默认为5
            this.priority = parent.getPriority();
            //线程对应类加载器
            if (security == null || isCCLOverridden(parent.getClass()))
                this.contextClassLoader = parent.getContextClassLoader();
            else
                this.contextClassLoader = parent.contextClassLoader;
            this.inheritedAccessControlContext =
                    acc != null ? acc : AccessController.getContext();
            //Runnable实例,相当于要运行的task
            this.target = target;
            setPriority(priority);
            //继承父线程inheritThreadLocals
            if (inheritThreadLocals && parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
            /* Stash the specified stack size in case the VM cares */
            this.stackSize = stackSize;
    
            /* Set thread ID */
            tid = nextThreadID();
        }
    

    除了安全管理器的访问检查稍微陌生(自行baidu、google),线程初始化相对比较简单,做了线程相关属性的初始化操作。

    Thread和Runnable的区别与联系

    Runnable是接口,用于声明Runnable实例是可以被线程调度运行的,只定义了一个run()方法:

    public interface Runnable {
        public abstract void run();
    }
    

    可以通过public Thread(Runnable target)、public Thread(Runnable target, String name)构造函数进行创建线程。
    Thread是类,实现了Runnable接口,这也说明了线程是可以被独立调度运行和分派的。

    public class Thread implements Runnable {
        ...
    
        /**
         * 实现Runnable,线程CPU被调度后代码执行入口
         */
        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    
        ...
    }
    

    从代码上看,直接通过无参构造函数创建线程并启动,实际上run()就相当于一个空方法。因此,如果不以Runnable实例创建线程,则应该通过继承Thread类覆盖run()方法的方式创建线程。接下来是常见的创建线程的两种方式。

    创建线程示例

    • 继承Thread类覆盖run()方法的方式
    public class Hello extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " Hello word !");
        }
    }
    //调用
    public class ThreadCreate {
        public static void main(String[] args) {
            //启动三个线程
            for (int i = 0; i < 3; i++) {
                //一定要注意的是调用start()方法启动线程,而非调用run()方法
                new Hello().start();
            }
        }
    }
    

    运行结果:

    Thread-0 Hello word !
    Thread-2 Hello word !
    Thread-1 Hello word !

    • 实现Runnable方式创建线程
      上边的代码修改为实现Runnable方式:
    public static class Hello implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " Hello word !");
        }
    }
    
    public class ThreadCreate {
        public static void main(String[] args) {
            for (int i = 0; i < 3; i++) {
                new Thread(new Hello()).start();
            }
        }
    }
    

    运行结果:

    Thread-0 Hello word !
    Thread-2 Hello word !
    Thread-1 Hello word !

    选择哪种方式

    通过继承的方式,Thread实现Runnable接口,即Thread是Runnable实例。从Thread实现Runnable接口代码片段看,如果target(Runnable实例)为空,run()相当于是一个空方法,继承覆盖的方式实际上就是重新实现了Runnable接口。
    target被多个线程共享,同一个任务由多个线程完成,发挥多线程的优势。拿Thread当Runnable实例感觉上总是有些别扭。
    所以,从面向接口编程的角度看推荐直接实现Runnable的方式创建线程。

    相关文章

      网友评论

          本文标题:常用创建线程的两种方式(3)

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