Java中实现多线程的方式

[TOC]

一、继承Thread类

继承Thread类,重写run方法,调用Thread的start()方法启动线程:

    /**
     * 实现线程方式:1、继承Thread类
     */
    @Slf4j
    public static class ThreadTarget extends Thread {
        @Override
        public void run() {
            while (true) {
                log.info("Thread extentions: " + System.currentTimeMillis());
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

new一个Thread实例的时候,所有构造方法最终都是调用Thread的init方法, init方法中,设置线程属性,校验相关权限:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        /**设置线程名字,匿名线程,名字自动生成:Thread-nextThreadNum()**/
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        /**设置线程组**/
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess(); //检查当前线程(parent)是否有权限修改线程组g
        if (security != null) { //校验是否thread子类的getContextClassLoader被覆盖
            if (isCCLOverridden(getClass())) { //并且拥有enableContextClassLoaderOverride权限
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        g.addUnstarted(); //增加线程组的unstarted线程计数
        this.group = g; //设置线程组
        /**继承当前线程(parant)的是否后台线程、设置优先级的值**/
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        /**设置contextClassLoader和继承accessControlContext**/
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        /**设置Runnable接口的实例,作为target对象**/        
        this.target = target;
        /**这里才是真正设置优先级的地方**/
        setPriority(priority);
        /**继承当前线程(parent)的ThreadLocal.ThreadLocalMap**/
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        this.stackSize = stackSize;
        /**设置线程id, 这里设置的是tid,跟nextThreadNum()设置的threadInitNumber是两个值**/
        tid = nextThreadID();
    }

启动线程需要调用start方法,方法中调用native方法start0真正启动线程,
虚拟机会在线程启动过程中回调Thread的run方法:

    public synchronized void start() {
        /**threadStatus不等于0, 状态异常,非NEW状态的线程*/
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        /**将线程加入到线程组,减线程组的unstarted计数器**/
        group.add(this);
        boolean started = false;
        try {
            /**native方法启动线程**/
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    /**移出线程组,增加unstarted计数器**/
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /** do nothing. start0的异常会直接抛出 **/
            }
        }
    }
    /**启动线程的native方法**/
    private native void start0();

  • Thread本身也实现了Runnable接口, 实现run方法,虚拟机启动时,调用的就是该实现方法。
  • 单纯从target看,Thread和Runnable的关系看,这是简单的模板方法模式的实现。
  • 这也引出了第二种实现方式,实现Runnable接口或者实现/继承Runnable接口的子接口/子类。
  • 所以继承Thread实现run和实现Runnable接口实现run方法是有本质区别的,Thread的run是被虚拟机调用的,Runnable的run是作为thread的target属性的模板方法被调用的。
    @Override
    public void run() {
        /**如果target不为空,调用target的run方法**/
        if (target != null) {
            target.run();
        }
    }

二、实现或继承Runnable接口的子接口或实现类

实现多线程的另外方式是实现或继承Runnable接口的子接口或实现类

1、继承Runnable接口

直接实现Runnable接口的问题是,线程执行完成后,无法获取到线程执行结果。

        /**
         * 实现线程方式: 2、实现Runnable接口
         */
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    log.info("Runnable implements 1: " + System.currentTimeMillis());
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        log.error(e.getMessage(), e);
                    }
                }
            }
        }); 
        thread2.start();
        /**
         * 实现线程方式: jdk1.8之后,可以更简单的写法
         */
        Thread thread3 = new Thread(() -> {
            while (true) {
                log.info("Runnable implements 2: " + System.currentTimeMillis());
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }); 
        thread3.start();

2、继承FutureTask接口

如果需要获取线程执行结果, 可以实现FutureTask接口, 并通过FutureTask的get方法获取执行结果:

        /**
         * 实现线程方式: 3、实现Callable接口,配合FutureTask,获取线程执行结果
         */
        FutureTask<Integer> task = new FutureTask<>(new Fibonacci(10));
        Thread thread4 = new Thread(task);
        thread4.start();
        try {
            Integer result = task.get();
            log.info("FutureTask result: {}" , result);
        } catch (ExecutionException e) {
            log.error(e.getMessage(), e);
        }

FutureTask相关的源码研究,后续分析。

3、继承TimerTask接口

如果需要在主线程外执行一些任务的话,可以使用TimerTask接口,配合定时器工具Timer实现,Timer内部维护一个TimerThread线程,用于执行调度任务:

        /**
         * 实现线程方式:4、调度执行任务
         */
        Timer timer = new Timer(); 
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                log.info("TimerTask execute: {}", System.currentTimeMillis());
            }
        }, 5000, 3000);

简单的应用可以使用Timer,复杂需求应该引入框架。

三、Executor框架

JDK1.5引入Executor异步执行框架,灵活强大,支持多种任务执行策略,将任务提交和执行解耦,可以通过submit和execute提交任务给线程池执行。Executors提供一系列创建线程池的工厂方法。

后续对Executor框架进行分析

        /**
         * 实现多线程的方式: 5、java.util.concurrent包提供的Executor框架,创建线程池,执行任务
         */
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //通过execute执行实现Runnable接口的任务
        for(int i = 0; i < 100; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("ThreadPool execute task: {} {}", 
                            Thread.currentThread().getName(), 
                            System.currentTimeMillis());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        log.error(e.getMessage(), e);
                    }
                }
            });
        }
        //也可以通过submit实现Callable接口的任务,以便获取线程执行结果
        Future<Integer> fiboResult = executorService.submit(new Fibonacci(10)); 
        try {
            log.info("ThreadPool submit task: {}",fiboResult.get());
        } catch (ExecutionException e) {
            log.error(e.getMessage(), e);
        }

四、ForkJoin框架

如果大任务可以分解成小任务并行计算,可以实现RecursiveTask接口,提交到ForJoin框架执行。

    public static void useForkJoinFramework() {
        long start = System.currentTimeMillis();
        ForkJoinPool pool = new ForkJoinPool(); 
        Long result = pool.invoke(createNewTask(0L, 10000000000L, 1000000000L));
        log.info("ForkJoinPool execute result: {} {}", result , (System.currentTimeMillis() - start));
        
        start = System.currentTimeMillis();
        long sum = 0; 
        for(long i = 0;i <= 10000000000L; i++) {
            sum += i; 
        }
        log.info("Simple sum: {} {}", sum, (System.currentTimeMillis() - start));
    }
    
    @SuppressWarnings("serial")
    public static RecursiveTask<Long> createNewTask(final Long start, final Long end, final Long critical){
        return new RecursiveTask<Long>() {
            @Override
            protected Long compute() {
                if(end - start <= critical) {
                    long sum = 0L; 
                    for(long l = start; l <= end; l++) {
                        sum += l; 
                    }
                    return sum; 
                }else {
                    Long middle = (end + start) / 2; 
                    RecursiveTask<Long> left = createNewTask(start, middle, critical);
                    RecursiveTask<Long> right = createNewTask(middle+1, end, critical);
                    left.fork();
                    right.fork();
                    return left.join()+right.join(); 
                }
            }
        };
    }

RecursiveTask接口继承自ForkJoinTask接口, ForkJoinTask继承Future接口。

五、总结

Java中多线程编程主要分两类:

  1. 通过创建Thread实例创建线程和start()方法启动线程,自己管理线程,执行任务;
  2. 通过Executor框架创建线程池,或者实现相关接口,通过线程池管理管理线程执行任务;
  3. 通过ForkJoin框架并行执行任务。
    不管是通过哪种方式, 都有支持Runnable接口提交任务和支持Callable接口的方式,jdk1.8之后,还可以通过lambda表达式提交任务,
    编程上弱化了Runnable和Callable的区别,所以选用哪种方式去创建线程和管理线程,取决于:
  • 场景是否足够简单,只需简单管理线程,还是需要强大的线程管理功能?
  • 是否需要线程执行结果,可以Future相关的API?
  • 是否可以并行执行?
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,084评论 0 23
  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 11,232评论 4 56
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,797评论 3 53
  • 在Java中,使用线程来异步执行任务。Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来...
    Steven1997阅读 746评论 0 0
  • 最近我和数学老师发现,我们班学生会忽然就表现好了,忽然就明白某件事该怎么做了,比如:我教他们背古诗时拍手,总有一些...
    木鱼飞木鱼飞阅读 377评论 0 1