天天看点

Java多线程感悟一

写在前面

有时候在项目开发中,时不时的会遇到多线程方面的问题。可是要想驾驭它,就必须了解Java多线程方面的知识,认识它的内在。结合我自己的开发经验以及对Java多线程的一些学习过程,将写几篇关于Java多线程的博客,分享给大家,如有错误,请大家指正。

目录

线程和进程

Thread和Runnable

线程的状态

生产者和消费者模型:wait/notify

后台线程

interrupt

线程合并join

UncaughtExceptionHandler

线程和进程的概念相信大家都知道,我来说下我的理解。操作系统可以将系统的一些资源,如CPU,内存等交给一段运行起来的程序,即进程。进程之间一般是相互独立的,比如Nginx的工作模型中有一个master进程,多个worker进程,如果有worker进程“挂”了,并不会影响其他worker进程处理请求。在进程内部可以有多个并发执行流,即线程。线程的存在必须依赖进程,同时一个进程内部的多个线程,它们共享进程的资源,相互之间可以影响。线程是轻量级的进程,它的创建、销毁、切换都比进程要小。

说起Java的多线程,我们脑海里面可能有如下的想法:

要么extends Thread,然后重写run(),调用start()

或者implements Runnable , 实现run(),将Runnable实例传递给Thread构造方法,调用start()

呵呵,其实,以前,我也是这么想的。

那么,在Java线程中,Thread和Runnable到底是什么,各自扮演着什么角色?

看一下Thread的类声明吧:

1

<code>public</code> <code>class</code> <code>Thread </code><code>implements</code> <code>Runnable</code>

从上面的来看,好像它们是“一家人”。

我们可以扫一扫Thread的源码,发现:

2

3

4

5

<code>/* What will be run. */</code>

<code>private</code> <code>Runnable target;</code>

<code>public</code> <code>Thread(Runnable target) {</code>

<code>init(</code><code>null</code><code>, target, </code><code>"Thread-"</code> <code>+ nextThreadNum(), </code><code>0</code><code>);</code>

<code>}</code>

其实,Runnable接口扮演的是一个任务的角色,通过提供run()来明确这个任务要做什么。对于Thread而言,默认情况下,它调用的就是你传递给它的Runnable的run()方法。Thread类中的target就是用来接收你传递进来的任务的,这样任务是任务,线程是线程,线程可以执行任务,就这么简单!

线程的状态很重要!

就我自己的感觉而言,我们写多线程方面的代码,就是在进行线程的状态转换从而完成业务上的要求,如果我们不清楚线程的状态,那么我们将无从下手,更加看不懂别人的代码!

根据我的理解,画了张图,一起来看下吧。

<a href="http://s3.51cto.com/wyfs02/M00/58/FA/wKioL1TDQY-hNnPdAACMYASm-2g174.jpg" target="_blank"></a>

线程有5个基本状态:新建、就绪、运行、阻塞、死亡。

当我们新建了一个Thread类的对象时,在JVM中只是存在了一个Thread对象而已,此时OS中并没有真正存在一个线程,只有一个操作线程的外壳,此时为新建状态。

如果我们调用了start()方法,那么将从新建状态转为就绪状态。start()方法的调用结束使得OS完成了内核调用,线程已经存在。需要注意的是,start()调用,run()方法一般并不会同步调用,因为run()的调用是CPU的选择,什么时候调用,我们无法准确干预。

当start()调用结束,系统相关资源准备完毕,那么就会进入运行状态,调用run()。如果在run过程中,失去了CPU时间片,将回到就绪状态。比如yield()方法,这个方法会让出CPU的使用权,希望给其他人用一用,当然这只是它的想法,也许yield()调用进入就绪状态后,马上CPU又调度他执行。

运行状态中,如果发生了sleep/等待锁/IO阻塞/wait等,将进入阻塞状态。如果在阻塞状态中,睡好了/锁来了/IO完成了/notify来了,那么就完成了阻塞状态的解除,从而进入到就绪状态,重新等待CPU的调度。

正常情况下,当run()结束或者发生了未捕获的异常,就会进入死亡状态。

前面说了那么多理论,下面我们就来点代码练练手吧!

业务场景:

有一个篮子,容量是10个,生产者生产馒头放入其中,消费者从篮子里面拿馒头吃。如果生产者发现篮子里面的馒头已经满了,就不再生产,消费者可以消费。如果消费者发现篮子里面的馒头空了,就停止消费,生产者可以生产。

馒头Model:

<code>class</code> <code>ManTou{</code>

篮子容器:

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

<code>class</code> <code>LanZhi{</code>

<code>private</code> <code>int</code> <code>size;</code>

<code>private</code> <code>int</code> <code>index = </code><code>0</code><code>;</code>

<code>private</code> <code>ManTou[] foods;</code>

<code>public</code> <code>LanZhi(</code><code>int</code> <code>size){</code>

<code>this</code><code>.size = size;</code>

<code>this</code><code>.foods = </code><code>new</code> <code>ManTou[size];</code>

<code>public</code> <code>synchronized</code> <code>void</code> <code>produce(ManTou m){</code>

<code>if</code><code>(index == size){</code>

<code>//不应该在生产馒头</code>

<code>try</code> <code>{</code>

<code>this</code><code>.wait();</code>

<code>} </code><code>catch</code> <code>(InterruptedException e) {</code>

<code>e.printStackTrace();</code>

<code>//馒头放入篮子</code>

<code>foods[index] = m;</code>

<code>++index;</code>

<code>System.out.println(</code><code>"生产一个馒头,现在馒头总共有:"</code> <code>+ index);</code>

<code>//发出通知:可以吃馒头了</code>

<code>this</code><code>.notify();</code>

<code>public</code> <code>synchronized</code> <code>ManTou eat(){</code>

<code>ManTou m = </code><code>null</code><code>;</code>

<code>if</code><code>(index == </code><code>0</code><code>){</code>

<code>//没得吃了</code>

<code>m = foods[--index];</code>

<code>System.out.println(</code><code>"吃掉一个馒头,现在馒头总共有:"</code> <code>+ index);</code>

<code>//发出通知:可以放入馒头了</code>

<code>return</code> <code>m;</code>

生产任务:

<code>class</code> <code>ProduceRunnable </code><code>implements</code> <code>Runnable{</code>

<code>private</code> <code>LanZhi l;</code>

<code>public</code> <code>ProduceRunnable(LanZhi l){</code>

<code>this</code><code>.l = l;</code>

<code>@Override</code>

<code>public</code> <code>void</code> <code>run() {</code>

<code>while</code><code>(</code><code>true</code><code>){</code>

<code>l.produce(</code><code>new</code> <code>ManTou());</code>

<code>//假设生产馒头耗时0.3S</code>

<code>Thread.sleep(</code><code>300</code><code>);</code>

消费任务

<code>class</code> <code>ConsumeRunnable </code><code>implements</code> <code>Runnable{</code>

<code>public</code> <code>ConsumeRunnable(LanZhi l){</code>

<code>l.eat();</code>

<code>//假设吃馒头耗时1S</code>

<code>Thread.sleep(</code><code>1000</code><code>);</code>

主线程调用:

<code>public</code> <code>static</code> <code>void</code> <code>main(String[] args) {</code>

<code>LanZhi l = </code><code>new</code> <code>LanZhi(</code><code>10</code><code>);</code>

<code>new</code> <code>Thread(</code><code>new</code> <code>ProduceRunnable(l)).start();</code>

<code>new</code> <code>Thread(</code><code>new</code> <code>ConsumeRunnable(l)).start();</code>

说明:

wait/notify这两个方法都是在object中定义的方法,都应该在临界区域内发生调用(也就是synchronized区域),否则会发生异常。一个线程好不容易拿到了“门票”,为什么要调用wait呢?因为有些时候,我们拿到了锁,准备进行一些操作的时候,发现并不满足业务上的一些要求,就需要wati了。就好比,我们进入了卫生间,关上了门,蹲在了马桶上,此时发现,没有带纸,没有办法,只好退出卫生间!

线程分为前台线程和后台线程。

首先需要注意的是,JVM认为一旦所有的前台线程运行完毕,只剩下后台线程的话,那么就意味着程序要结束了。

举个例子:

<code>class</code> <code>Daemon </code><code>extends</code> <code>Thread{</code>

<code>for</code><code>(</code><code>int</code> <code>i = </code><code>0</code> <code>; i &lt; </code><code>10</code> <code>; i++){</code>

<code>System.out.println(</code><code>"i : "</code> <code>+ i);</code>

<code>Daemon d = </code><code>new</code> <code>Daemon();</code>

<code>d.setDaemon(</code><code>true</code><code>);</code>

<code>d.start();</code>

<code>System.out.println(</code><code>"hello"</code><code>);</code>

<code>运行结果:</code>

<code>hello</code>

<code>i : </code><code>0</code>

<code>i : </code><code>1</code>

<code>i : </code><code>2</code>

<code>i : </code><code>3</code>

<code>i : </code><code>4</code>

<code>i : </code><code>5</code>

<code>i : </code><code>6</code>

<code>i : </code><code>7</code>

hello的输出,意味着前台线程已经运行完毕了,那么JVM将会结束整个进程,并且不保证执行完后台线程。那么后台线程可以用来干嘛?比如Java的垃圾回收线程就是一个后台线程,而且它的调度优先级比较低,它不会随便去和其他线程争夺使用CPU。

interrupt,即中断的意思,那中断意味着什么?

我们先看看下面的:

<code>public</code> <code>final</code> <code>native</code> <code>void</code> <code>wait(</code><code>long</code> <code>timeout) </code><code>throws</code> <code>InterruptedException;</code>

<code>public</code> <code>static</code> <code>native</code> <code>void</code> <code>sleep(</code><code>long</code> <code>millis) </code><code>throws</code> <code>InterruptedException;</code>

<code>public</code> <code>final</code> <code>synchronized</code> <code>void</code> <code>join(</code><code>long</code> <code>millis)  </code><code>throws</code> <code>InterruptedException</code>

我们知道有些方法会抛出中断异常,那什么时候抛呢?

再去扫一眼Thread源码,发现:

<code> </code><code>public</code> <code>static</code> <code>boolean</code> <code>interrupted() {</code>

<code>return</code> <code>currentThread().isInterrupted(</code><code>true</code><code>);</code>

<code> </code><code>}</code>

<code> </code><code>public</code> <code>boolean</code> <code>isInterrupted() {</code>

<code>return</code> <code>isInterrupted(</code><code>false</code><code>);</code>

<code> </code><code>private</code> <code>native</code> <code>boolean</code> <code>isInterrupted(</code><code>boolean</code> <code>ClearInterrupted);</code>

在默认情况下,显然,线程的中断标志是false。当我们调用了interrupted()后,实际上是将线程中断标志设置为true。

看一个小例子:

<code>class</code> <code>Thread1 </code><code>extends</code> <code>Thread{</code>

<code>Thread.sleep(</code><code>10000</code><code>);</code>

<code>//其他一些业务操作</code>

<code>System.out.println(</code><code>"sleep over..."</code><code>);</code>

<code>System.out.println(</code><code>"出现中断异常"</code><code>);</code>

<code>Thread1 t1 = </code><code>new</code> <code>Thread1();</code>

<code>t1.start();</code>

<code>t1.interrupt();</code>

运行结果:

出现中断异常

说明interrupt()的调用,只是做了一个标记并产生异常抛出而已,并没有真正的唤醒线程。

线程合并?为什么多个线程要合并?什么时候会出现需要合并呢?怎么合并?

假设有这么一个业务排序场景:

我们有N个这样的块,每个块中存放数字,而且块间有序、块内是无序的,如下:

第一块数字范围:[0,1000]

第二块数字范围:[1001,2000]

第三块数字范围:[2001,3000]

...

分析:

不应该对所有块的所有数字来一次全排序,很显然的是,我们想利用块间有序这个条件,能不能对每一块进行排序。要知道,第一块排序和第二块排序,他们是相互独立的,没有锁的限制,完全可以并行进行。那我们的初步想法是,启动N个线程对N个块进行排序,当N个线程都结束,就完成了排序了。但是要知道,我们无法保证每个排序线程的完成顺序,但是业务上,我们确实需要当排序完毕后进行XXX操作。

当然,我们可以写一个循环进行遍历,来获取N个排序线程的状态,从而决定排序完毕后的操作什么时候进行。其实,Java已经提供了join(),来完成这方面的需要。

实例如下:

<code>class</code> <code>SortRunnable </code><code>implements</code> <code>Runnable{</code>

<code>int</code><code>[] array;</code>

<code>public</code> <code>SortRunnable(</code><code>int</code><code>[] array){</code>

<code>this</code><code>.array = array;</code>

<code>for</code><code>(</code><code>int</code> <code>i = </code><code>0</code> <code>; i &lt; array.length ; i++){</code>

<code>for</code><code>(</code><code>int</code> <code>j = i+</code><code>1</code> <code>; j &lt; array.length ; j++){</code>

<code>if</code><code>(array[i] &gt; array[j]){</code>

<code>int</code> <code>tmp = array[i] ^ array[j];</code>

<code>array[i] = tmp ^ array[i];</code>

<code>array[j] = tmp ^ array[j];</code>

<code>int</code><code>[] array1 = {</code><code>1</code><code>,</code><code>9</code><code>,</code><code>8</code><code>,</code><code>7</code><code>};</code>

<code>int</code><code>[] array2 = {</code><code>10</code><code>,</code><code>30</code><code>,</code><code>25</code><code>};</code>

<code>Thread t1 = </code><code>new</code> <code>Thread(</code><code>new</code> <code>SortRunnable(array1));</code>

<code>Thread t2 = </code><code>new</code> <code>Thread(</code><code>new</code> <code>SortRunnable(array2));</code>

<code>t2.start();</code>

<code>t1.join();</code>

<code>t2.join();</code>

<code>for</code><code>(</code><code>int</code> <code>tmp : array1){</code>

<code>System.out.print(tmp + </code><code>" , "</code><code>);</code>

<code>System.out.println();</code>

<code>for</code><code>(</code><code>int</code> <code>tmp : array2){</code>

直接看一个例子,大家就能明白!

<code>class</code> <code>MyException </code><code>implements</code> <code>UncaughtExceptionHandler{</code>

<code>public</code> <code>void</code> <code>uncaughtException(Thread t, Throwable e) {</code>

<code>System.out.println(</code><code>"捕捉到线程中未处理的异常"</code><code>);</code>

<code>Thread t = </code><code>new</code> <code>Thread(){</code>

<code>//业务操作</code>

<code>//...</code>

<code>System.out.println(</code><code>"start..."</code><code>);</code>

<code>Integer.parseInt(</code><code>"a"</code><code>);</code>

<code>System.out.println(</code><code>"end..."</code><code>);</code>

<code>};</code>

<code>t.setUncaughtExceptionHandler(</code><code>new</code> <code>MyException());</code>

<code>t.start();</code>

start...

捕捉到线程中未处理的异常

如果在run()中出现了我们没有处理的异常,那么我们还有一次“后悔药”可以吃~

本文转自zfz_linux_boy 51CTO博客,原文链接:http://blog.51cto.com/zhangfengzhe/1607712,如需转载请自行联系原作者