(如何创建线程)

Posted 风清扬逍遥子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(如何创建线程)相关的知识,希望对你有一定的参考价值。

        上节讲了基础概念,本章正式进入线程专题,对基础薄弱的同学可以好好看本章!!

1、Thread匿名子类

        我们可以通过下面的代码来直接创建一个线程。

// 构造方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") 
 @Override
 // run 方法内实现了要执行的任务
 public void run() 
 log.debug("hello");
 
;
t1.start();

        需要注意下:

  • 创建线程是不是立马就执行?

        当然不是,当调用t1.start()的时候,虚拟机先执行main方法,所以会有main线程去执行代码,而执行到Thread的时候,发现要开辟一个线程,于是会创建一个新线程,等待cpu分配时间片去调度,而start方法两个作用:新创建个线程+此线程进入就绪状态等待被执行

2、实现Runnable接口

        我们也可以通过这个方式去创建线程:

Runnable runnable = new Runnable() 
 public void run()
 // 要执行的任务
 
;
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start(); 

        和第一种方式相比,其实有很大的不同,这种方式是【任务】和【线程】分开,Runnable实际在这里表示可运行的任务,比如下面的代码执行:Thread一定是要有的,而Thread的有参构造,传入当前要执行的任务对象包给这个线程去执行,同时也可以给予开辟的线程名称。

        在Java8的Lambda表达式中,可以简化写法,这里我不多说Lambda表达式自行了解。 

        我们分析Thread的源码,理清它与 Runnable 的关系,当Runnable对象不为空,Thread构造方法传递Runnable对象,底层会调用Runnable对象的run方法进行任务的创建。

         而直接new Thread的方式实际就是创建一个子类对象,重写父类的run方法,最终会调用子类中的run方法,所以在底层执行中,走的都是底层的run方法。

        区别在于:

        new Thread是把线程和任务合并在了一起

        实现Runnable是把线程和任务分开了,用 Runnable 更容易与线程池等高级 API 配合

        用 Runnable 让任务类脱离了 Thread 继承体系,更灵活。

3、FutureTask配合Thread

        先看下FutureTask的结构:很明显实现了Runnable接口,也是个任务对象;另外多了一个Future接口,用来返回任务的执行相关信息的。

         由于Runnable的run方法是void类型,并不能在两个线程中把一个结果传递给另一个线程,而Future接口中的get方法是可以用来返回结果,怎么用这个FutureTask创建一个任务对象呢?

        因为FutureTask<V>是个泛型,且get方法返回也是个泛型,在FutureTask的构造中传递参数是Callable接口;

        其中的call方法是具备返回值的。

         于是我们可以这么写,这里有个比较关键的一个点,当主线程运行到get的时候,会阻塞等待,直到这个task3执行完后返回了,主线程才可以拿到task3执行后返回的结果并输出。

// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> 
 log.debug("hello");
 return 100;
);
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:", result);

4、思考为什么有了Thread还出现Runnable?

        我觉得这个问题会有很多基础不扎实的同学搞不明白,甚至工作很多年的都会是一脸蒙蔽,我这里就说说我个人的观点:

        首先Thread出现的时候,在使用方面,通过继承的方式去重写父类Thread的run方法,达到线程执行逻辑的目的,那么如果一个类A已经继承过了其他的父类B,你再想去让某个线程在这个类执行里面部分代码,你该怎么做?

        要么你选择单独开个类C,继承Thread,然后类A的逻辑cp一份到这个C类上走下Thread.start方法;

        要么改逻辑?不现实吧,那么Runnable出现,这个时候解决了这个问题,没错,Java中不能多继承,只能单继承的机制,但是可以多实现,好了我可以选择让当前这个A类implements Runanble,并且可以重写里面的run方法,而要执行的代码逻辑,是不是可以抽出一个方法,并且在这个run中调用这个方法就可以了?这是一点

        还有就是,Thread t1 = new Thread子类,这个子类里会复写父类的方法,完成业务逻辑,发现没有,这个线程只能干这个事情,没法做到和其他线程共享处理某个任务,不信你就可以试试看。如果我想做多个线程处理同一个业务逻辑,我无法做到,比如,多个窗口卖票!

        那么有了Runnable的好处,我可以定义某个任务,这个任务就是去扣除票的,那么Thread提供了相关的Runnable的构造,就表示可以有多个Thread执行这个任务,就可以达到多个线程执行相同的任务效果,至于其中的临界资源控制,不在这个范围中。

        所以,好处一定是存在,但是认不认识到,那是另一个事情。

        创建线程的三种主要方式就介绍到这。后面剖析线程原理

以上是关于(如何创建线程)的主要内容,如果未能解决你的问题,请参考以下文章

iOS-多线程之NSThread详解

C#怎么开辟多线程,要是多了是不是会出错,出错了怎么办?

开辟线程的两种方式

创建一个对象都在内存中做了什么事情

Java线程详解

//timer 开辟新线程,不占用主线程