Java线程篇——线程的开启

原文做者:Ykamh编程

原文地址:juejin.im/post/5bd3c2…多线程

阅读时长:20分钟并发

技术预备:Java基础socket

随着开发项目中业务功能的增长,必然某些功能会涉及到线程以及并发编程的知识点。笔者就在如今的公司接触到了不少软硬件结合和socket通信的项目了,不少的功能运用到了串口通信编程,串口通信编程的安卓端就是基于线程的方式和硬件保持通信的。post

关于Java线程,先了解一下Java线程的生命周期和物种基本状态,先上一张经典的图线程

上图也比较直观的绘制了关于Java线程的生命周期同时也囊括了Java线程的重点知识点。cdn

Java线程的五种状态:

新建状态(New):

当线程对象建立后,线程即进入新建状态,如:Thread t = new MyThread();对象

就绪状态(Runnable):

当线程对象的start()方法(t.start();)调用时,此时线程进入就绪状态。处于就绪状态的线程只能说明线程此时已经作好准备,随时等待CPU的调度并非说 t.start(): 后线程就会立马执行;blog

运行状态(Running):

当CPU开始调度已经处于就绪状态的线程时,此时线程才真正的开始他的工做,即进入了运行状态。注意(敲黑板!):就绪状态是进入到运行状态的惟一入口。也就是说线程想进入运行状态,那线程就必须先处于就绪状态。继承

阻塞状态(Blocked):

处于运行状态的线程处于某种缘由呢,暂时放弃了对CPU的使用权,中止了执行,此时也就进入了阻塞状态,知道线程再次进入到就绪状态,才有机会被CPU调用进入到运行状态。而根据形成阻塞的缘由不一样,分为了一下三种阻塞:

  • 等待阻塞:运行状态的线程执行到了wate()方法,使线程进入了等待阻塞状态。

  • 同步阻塞:线程在获取synchronized同步锁失败(由于锁被其余线程占用),他就会进入同步阻塞状态。

  • 其余阻塞:经过线程的sleep()和join()或发出了I/O请求,线程进入到了阻塞状态。当sleep()超时、join()等待线程终止或超时、I/O处理完毕时,线程就会再次进入就绪状态。

死亡状态(Dead):

线程执行完了或者在执行中因异常退出了run()方法,该线程就走完了他的一辈子了。

Java多线程的建立和启动

Java线程有三种常见的基本建立方式

继承Thread类,重写其run()方法

啊,正如上述代码所示,经过继承Thread类重写其run()方法,定义了一个新的线程类MyThread,其中咱们所重写的run()方法体内的代码就是线程须要完成的任务了,专业点的来讲咱们称之为线程执行体。当建立此线程类对象时一个新的线程得以建立,并进入到线程新建状态。经过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不必定会立刻得以执行,这取决于CPU调度时机。

实现Runnable接口,并重写实现其run()方法

没必要想太多,这个run()方法一样是咱们的线程执行体。重写完run方法后建立接口实例,并把该实例做为建立线程的target建立线程。没看懂不要紧,直接上代码。

其实跟第一种方法也差很少的。那么Thread和Runnable之间究竟是什么关系呢?咱们再来看一个例子:

看起来有点奇怪,其实一样是能够建立线程的。那么聪明敏锐的你确定会在脑瓜里面有个提问:那此时线程究竟是执行MyRunnable里面的执行体呢仍是执行MyThread里面的执行体呢?答案是MyThread里的执行体被执行。由于Thread类自己也是实现了Runnable接口,而run()方法最早是在Runnable接口中定义的方法。

从上面的代码咱们不难看出,当执行到Thread类中的run()方法时,会首先判断target是否存在,存在则执行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。可是上述给到的列子中,因为多态的存在,根本就没有执行到Thread类中的run()方法,而是直接先执行了运行时类型即MyThread类中的run()方法。

用Callable和Future接口建立线程

具体是建立Callable接口的实现类,并实现call()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象做为Thread对象的target来建立线程。有点绕~直接看代码吧~

咱们很容易看得出来,此时的线程执行体再也不是run()方法了,取而代之的是call()方法,而且这个方法是有返回的。在建立新的线程时,是经过FutureTask来包装MyCallable对象,同时做为了Thread对象的target。那么看下FutureTask类的定义:

这样咱们就能清楚的看得出他们之间的关系。发现FutureTask类其实是同时实现了Runnable和Future接口,使得他有了双重特性了,经过Runnable特性,能够做为Thread对象的target,而Future特性,使得其能够取得新建立线程中的call()方法的返回值。

执行下此程序,咱们发现sum = 4950永远都是最后输出的。而“主线程for循环执行完毕..”则极可能是在子线程循环中间输出。由CPU的线程调度机制,咱们知道,“主线程for循环执行完毕..”的输出时机是没有任何问题的,那么为何sum =4950会永远最后输出呢?

缘由在于经过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。

上述主要讲解了三种常见的线程建立方式,对于线程的启动而言,都是调用线程对象的start()方法,须要特别注意的是:不能对同一线程对象两次调用start()方法。

线程的开启暂时就讲这么多了,后面的文章还会继续讲述Java线程之如何优雅的关闭线程。

That's all Thank you~

----- End -----

更多好文

请扫描下面二维码

欢迎关注~

相关文章
相关标签/搜索