博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
创建多线程的三种方式
阅读量:6292 次
发布时间:2019-06-22

本文共 5585 字,大约阅读时间需要 18 分钟。

在java中给我们提供了三种方式来创建多线程。前两种是我们比较常见的,第三种是JDK1.5之后提供给我们的。接下来我们详细的看一下这三种创建线程的写法。

继承Thread类

第一种方式是继承Thread类的写法,代码如下:
Thread thread = new Thread(){            @Override            public void run() {                for(int i=0;i<10;i++){                    System.out.println("线程创建的第一种方式:"+Thread.currentThread().getName());                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        };        thread.start();
注意这里我们覆盖的是run方法,而不是start方法,并且也千万不要覆盖start方法。为什么不能覆盖start方法呢?我们看一下Thread源码中start方法是怎么写的:
public synchronized void start() {        /**         * This method is not invoked for the main method thread or "system"         * group threads created/set up by the VM. Any new functionality added         * to this method in the future may have to also be added to the VM.         *         * A zero status value corresponds to state "NEW".         */        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 */            }        }    }
注意这个方法是加锁的方法。在这个方法中最重要的一段代码是:start0();我们接着再来看一下start0()这个方法:
private native void start0();
它是个native方法。就是这个native的start0()方法,它实现了启动线程,申请栈内存、运行run方法、修改线程状态等职责。线程管理和栈内存管理都是由jvm负责的。如果你覆盖了start方法,也就是撤销了线程管理和栈内存管理的能力,这样还如何启动一个线程呢?不过Thread的这个设计是很精妙的,因为你只需要关注你的业务逻辑就行了,而对于线程和栈内存的管理都有JVM来做就行了。
如果在你的开发中不得不要覆盖start方法的话,请千万要记得调用super.start(),要不然你的线程无法启动。

实现Runnable接口

第二种创建线程的方式是实现Runnable接口。具体代码请看下面:
Thread thread2 = new Thread(new Runnable() {            @Override            public void run() {                for(int i=0;i<10;i++){                    System.out.println("线程创建的第二种方式:"+Thread.currentThread().getName());                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        });        thread2.start();
这种写法是实现了Runnable接口的一种写法。它的原理是什么呢?我们来看一下Thread源码中run的写法:
@Override    public void run() {        if (target != null) {            target.run();        }    }
在run方法中我们可以看到如果target != null就调用target.run()方法。而这个target是从哪儿来的呢?我们继续看Thread的源码,发现在init的方法中有这样一句话:
this.target = target;
接下来我们再看init()这个方法是在哪儿被调用的?通过翻读源码我们可以发现在Thread的每一个构造函数中都会调用init这个方法,并且有这样一个构造函数:
public Thread(Runnable target) {        init(null, target, "Thread-" + nextThreadNum(), 0);    }
看到了吧。我们就是在创建Thread的时候,通过Thread的构造函数传递进去的Runnable的实现类。而线程启动的时候,调用run方法,run方法又接着调用Runnable实现类的run方法!!!!

实现Callable接口

在JDK1.5之后又给我们提供了一种新的创建线程的方式:实现Callable方法。具体代码如下:
FutureTask
ft = new FutureTask
(new Callable
() { @Override public Integer call() throws Exception { int i = 0; for(;i<10;i++){ System.out.println("线程创建的第三种方式:"+Thread.currentThread().getName()); } return i; } }); new Thread(ft).start();
在上面的代码中,在创建FutureTask对象的时候,我们把Callable的一个匿名实现类当做参数传到了FutureTask的构造函数中,而在启动线程的时候,我们又把创建的FutureTask的对象当做参数传到了Thread的构造函数中。在这里请注意我们此时不是覆盖的run方法,而是一个叫call的方法。大家可能会感到疑惑这个FutureTask和Callable这两个到底是个什么玩意?下面我们一个一个的分析:

FutureTask

通过翻读FutureTask的源码我们可以看出来实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable和Future接口。注意:这里是继承了两个接口!你可能会有疑问JAVA中不是没有多继承吗?不错,java中类是没有多继承的,而对于接口是有多继承的!!!到这里我们明白一件事,那就是FutureTask是Runnable接口的一个实现类。到这里你是不是明白了点什么呢?
public class FutureTask
implements RunnableFuture
public interface RunnableFuture
extends Runnable, Future
如果你不明白的话,那就多看几遍第二种创建线程的方式和它的原理吧。接下来我们来看一下FutureTask这个类中的run方法是怎么写的:
public void run() {        if (state != NEW ||            !UNSAFE.compareAndSwapObject(this, runnerOffset,                                         null, Thread.currentThread()))            return;        try {            Callable
c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call();
上面这个方法中的代码我没有贴全,只贴出来了主要的部分。在这个方法中我们会发现这样的两句话Callable<V> c = callable;result = c.call();这两句话就是关键!!!通过翻读源码我们就会发现源码这个callable就是我们刚才传到FutureTask中的Callable的实现类啊!
public FutureTask(Callable
callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable }
c.call()那不就是调用的Callable实现类的call方法吗?!!!到这里终于真相大白了!FutureTask中其他方法有兴趣的同学可以继续研究一下。

Callable

在Callable这个接口中只有一个call方法。

总结

在实际编码中,我们看到创建线程更多的是使用第二种方式,因为它更符合java中面向接口编程的思想。
最后出个题考一下大家,请说出下面代码的运行结果:
new Thread(new Runnable() {            @Override            public void run() {                System.out.println("Runnable实现类的调用:");            }        }){            @Override            public void run() {                System.out.println("继承Thread的调用:");            }        }.start();

转载地址:http://dxjta.baihongyu.com/

你可能感兴趣的文章
新手 开博
查看>>
借助开源工具高效完成Java应用的运行分析
查看>>
163 yum
查看>>
第三章:Shiro的配置——深入浅出学Shiro细粒度权限开发框架
查看>>
80后创业的经验谈(转,朴实但实用!推荐)
查看>>
让Windows图片查看器和windows资源管理器显示WebP格式
查看>>
我的友情链接
查看>>
vim使用点滴
查看>>
embedded linux学习中几个需要明确的概念
查看>>
mysql常用语法
查看>>
Morris ajax
查看>>
【Docker学习笔记(四)】通过Nginx镜像快速搭建静态网站
查看>>
ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务
查看>>
<转>云主机配置OpenStack使用spice的方法
查看>>
java jvm GC 各个区内存参数设置
查看>>
[使用帮助] PHPCMS V9内容模块PC标签调用说明
查看>>
关于FreeBSD的CVSROOT的配置
查看>>
基于RBAC权限管理
查看>>
数学公式的英语读法
查看>>
留德十年
查看>>