|
1 |
| -在上一篇文章中[http://www.jianshu.com/p/959cf355b574](http://www.jianshu.com/p/959cf355b574 "为什么要学习并发编程")谈到了为什么花功夫去学习并发编程的技术,也就是说我们必须了解到并发编程的优缺点,我们在什么情况下可以去考虑开启多个线程去实现我们的业务,当然使用多线程我们应该着重注意一些什么,在上一篇文章中会有一些讨论。那么,说了这么多,无论是针对面试还是实际工作中作为一名软件开发人员都应该具备这样的技能。万事开头难,接下来就应该了解如何新建一个线程?线程状态是怎样转换的?关于线程状态的操作是怎样的?这篇文章就主要围绕这三个方面来聊一聊。 |
| 1 | +在上一篇文章中 |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +谈到了为什么花功夫去学习并发编程的技术,也就是说我们必须了解到并发编程的优缺点,我们在什么情况下可以去考虑开启多个线程去实现我们的业务,当然使用多线程我们应该着重注意一些什么,在上一篇文章中会有一些讨论。那么,说了这么多,无论是针对面试还是实际工作中作为一名软件开发人员都应该具备这样的技能。万事开头难,接下来就应该了解如何新建一个线程?线程状态是怎样转换的?关于线程状态的操作是怎样的?这篇文章就主要围绕这三个方面来聊一聊。 |
2 | 8 | # 1. 新建线程 #
|
3 | 9 | 一个java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上java程序天生就是一个多线程程序,包含了:(1)分发处理发送给给JVM信号的线程;(2)调用对象的finalize方法的线程;(3)清除Reference的线程;(4)main线程,用户程序的入口。那么,如何在用户程序中新建一个线程了,只要有三种方式:
|
4 | 10 |
|
|
52 | 58 | - 由于java不能多继承可以实现多个接口,因此,在创建线程的时候尽量多考虑采用实现接口的形式;
|
53 | 59 | - 实现callable接口,提交给ExecutorService返回的是异步执行的结果,另外,通常也可以利用FutureTask(Callable<V> callable)将callable进行包装然后FeatureTask提交给ExecutorsService。如图,
|
54 | 60 |
|
55 |
| - |
| 61 | + |
| 62 | + |
| 63 | + |
56 | 64 | 另外由于FeatureTask也实现了Runable接口也可以利用上面第二种方式(实现Runable接口)来新建线程;
|
57 | 65 | - 可以通过Executors将Runable转换成Callable,具体方法是:Callable<T> callable(Runnable task, T result), Callable<Object> callable(Runnable task)。
|
58 | 66 |
|
59 | 67 | # 2. 线程状态转换 #
|
60 | 68 |
|
61 | 69 |
|
62 |
| - |
| 70 | + |
| 71 | + |
| 72 | + |
63 | 73 |
|
64 | 74 | 此图来源于《JAVA并发编程的艺术》一书中,线程是会在不同的状态间进行转换的,java线程线程转换图如上图所示。线程创建之后调用start()方法开始运行,当调用wait(),join(),LockSupport.lock()方法线程会进入到**WAITING**状态,而同样的wait(long timeout),sleep(long),join(long),LockSupport.parkNanos(),LockSupport.parkUtil()增加了超时等待的功能,也就是调用这些方法后线程会进入**TIMED_WAITING**状态,当超时等待时间到达后,线程会切换到Runable的状态,另外当WAITING和TIMED _WAITING状态时可以通过Object.notify(),Object.notifyAll()方法使线程转换到Runable状态。当线程出现资源竞争时,即等待获取锁的时候,线程会进入到**BLOCKED**阻塞状态,当线程获取锁时,线程进入到Runable状态。线程运行结束后,线程进入到**TERMINATED**状态,状态转换可以说是线程的生命周期。另外需要注意的是:
|
65 | 75 |
|
66 | 76 | - 当线程进入到synchronized方法或者synchronized代码块时,线程切换到的是BLOCKED状态,而使用java.util.concurrent.locks下lock进行加锁的时候线程切换的是WAITING或者TIMED_WAITING状态,因为lock会调用LockSupport的方法。
|
67 | 77 |
|
68 | 78 | 用一个表格将上面六种状态进行一个总结归纳。
|
69 | 79 |
|
70 |
| - |
| 80 | + |
| 81 | + |
71 | 82 |
|
72 | 83 | # 3. 线程状态的基本操作 #
|
73 | 84 | 除了新建一个线程外,线程在生命周期内还有需要基本操作,而这些操作会成为线程间一种通信方式,比如使用中断(interrupted)方式通知实现线程间的交互等等,下面就将具体说说这些操作。
|
|
78 | 89 | isInterrupted()来感知其他线程对其自身的中断操作,从而做出响应。另外,同样可以调用Thread的静态方法
|
79 | 90 | interrupted()对当前线程进行中断操作,该方法会清除中断标志位。**需要注意的是,当抛出InterruptedException时候,会清除中断标志位,也就是说在调用isInterrupted会返回false。**
|
80 | 91 |
|
81 |
| - |
| 92 | + |
| 93 | + |
| 94 | + |
| 95 | + |
82 | 96 |
|
83 | 97 | 下面结合具体的实例来看一看
|
84 | 98 |
|
@@ -244,4 +258,4 @@ public static native void yield();这是一个静态方法,一旦执行,它
|
244 | 258 | > at java.lang.Thread.setDaemon(Thread.java:1365)
|
245 | 259 | > at learn.DaemonDemo.main(DaemonDemo.java:19)
|
246 | 260 |
|
247 |
| -这样的异常,但是该线程还是会执行,只不过会当做正常的用户线程执行。 |
| 261 | +这样的异常,但是该线程还是会执行,只不过会当做正常的用户线程执行。 |
0 commit comments