多线程实战(一)——多线程轮流调用
2016-08-01 19:41
260 查看
师傅留了一个作业让我们来熟悉多线程问题,原本对多线程一直处于理论阶段,大二学操作系统的时候写的也是一知半解,今天拿到这道题又好好的做了一遍。
题目:审核系统有一批工单需要处理,现在启动三个线程进行处理,要求线程1处理工单id mod 3 = 1的工单,线程2处理工单id mod 3 = 2的工单,线程3处理工单id mod 3 = 0的工单,直到工单全部处理完毕,假设工单有1000个,工单编号从1-1000,工单处理过程简化为打印工单id,要求工单必须按顺序处理,即打印结果必须保证从1-1000从小到大递增
1、请使用原始synchronized,wait(),notify(),notifyAll()等方式来实现。
2、使用JDK1.5并发包提供的Lock,Condition等类的相关方法来实现。
对于第一个问题,网上有很多相关的代码,有重写三个run方法的,甚至有创建1000个进程的,很多都不是特别的好。受到一篇博文的启发@zyplus,我写出了下面的代码,优势之处在于传参的话只需要重写一个run方法,代码也相对优美一些。具体思想是这样的,其实我们要做的知识对3个线程的循环调用,为了控制三个线程的前后顺序,需要定义两个锁,一个是prev,即前一个线程持有的对象锁,第二个是self,用来使下一个线程进行等待和唤醒状态转换。
await(): 使当前线程在接到信号或被中断之前处于等待状态。
signal(): 唤醒一个等待线程。
signalAll(): 唤醒所有的等待线程。
这样的话,Condition承担的工作其实就是代替了被锁锁住的对象,使它的使用更加灵活且更加好理解一些了。
本来我还是想把代码写成和上一个代码相同的思想的,这样会大大的简化代码,但是过程中发现,在线程中需要使用到Condition对象,觉得传的参数有点小多,就多重写几个run吧(在写这段话的时候我把这两份代码又进行了一次比较,觉得还是第一份代码写得好,尝试着把第二份代码也像上一个那样去写,虽然好像传的参数有点多,出了点小bug,公司没大有人了,先把这篇先写完吧)
t1等待
t1处理:1
t1等待
t2等待
t3等待
死锁,为什么会发生这种情况,因为在t1中执行reachTwoCondition.signal();的时候t2中的reachTwoCondition还没进入等待状态,我学习的那篇博文的博主教导我们把线程的顺序倒过来start不就行了么,一开始我觉得挺有道理的,也像他那样做了,运行一下,也挺成功的,后来有运行了几次,发现成功到1000是个概率事件,经常会在500多呀800多呀甚至30多发生死锁。为什么,因为循环一定的次数之后总会有概率发生之前这样t1中执行reachTwoCondition.signal();的时候t2中的reachTwoCondition还没进入等待状态,t2t3同样状态的情况,这就很尴尬了,我目前的解决办法是
题目:审核系统有一批工单需要处理,现在启动三个线程进行处理,要求线程1处理工单id mod 3 = 1的工单,线程2处理工单id mod 3 = 2的工单,线程3处理工单id mod 3 = 0的工单,直到工单全部处理完毕,假设工单有1000个,工单编号从1-1000,工单处理过程简化为打印工单id,要求工单必须按顺序处理,即打印结果必须保证从1-1000从小到大递增
1、请使用原始synchronized,wait(),notify(),notifyAll()等方式来实现。
2、使用JDK1.5并发包提供的Lock,Condition等类的相关方法来实现。
对于第一个问题,网上有很多相关的代码,有重写三个run方法的,甚至有创建1000个进程的,很多都不是特别的好。受到一篇博文的启发@zyplus,我写出了下面的代码,优势之处在于传参的话只需要重写一个run方法,代码也相对优美一些。具体思想是这样的,其实我们要做的知识对3个线程的循环调用,为了控制三个线程的前后顺序,需要定义两个锁,一个是prev,即前一个线程持有的对象锁,第二个是self,用来使下一个线程进行等待和唤醒状态转换。
public class GongdanHandler extends Thread { private String name; private Object prev; private Object self; private static int id = 1; private GongdanHandler(String name, Object prev, Object self) { this.name = name; this.prev = prev; this.self = self; } public void run() { while (id <= 1000) { synchronized (prev) { synchronized (self) { System.out.println(name + "处理工单" + id); id++; self.notify(); } try { prev.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws Exception { Object a = new Object(); Object b = new Object(); Object c = new Object(); GongdanHandler ta = new GongdanHandler("线程1", c, a); GongdanHandler tb = new GongdanHandler("线程2", a, b); GongdanHandler tc = new GongdanHandler("线程3", b, c); ta.start(); tb.start(); tc.start(); } }对于第二个问题,就有点头疼了,看了不少对于Lock和Condition的介绍和代码,感觉还是有点懵逼,对于Condition,作为新手的我看了这些方法
await(): 使当前线程在接到信号或被中断之前处于等待状态。
signal(): 唤醒一个等待线程。
signalAll(): 唤醒所有的等待线程。
这样的话,Condition承担的工作其实就是代替了被锁锁住的对象,使它的使用更加灵活且更加好理解一些了。
本来我还是想把代码写成和上一个代码相同的思想的,这样会大大的简化代码,但是过程中发现,在线程中需要使用到Condition对象,觉得传的参数有点小多,就多重写几个run吧(在写这段话的时候我把这两份代码又进行了一次比较,觉得还是第一份代码写得好,尝试着把第二份代码也像上一个那样去写,虽然好像传的参数有点多,出了点小bug,公司没大有人了,先把这篇先写完吧)
package test; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Created by bjhl on 16/8/1. */ public class GongdanHandler2 extends Thread{ static class NumberWrapper { public static int value = 1; } public static void main(String[] args) { //初始化可重入锁 final Lock lock = new ReentrantLock(); final Condition reachOneCondition = lock.newCondition(); final Condition reachTwoCondition = lock.newCondition(); final Condition reachThreeCondition = lock.newCondition(); final NumberWrapper num = new NumberWrapper(); Thread t1 = new Thread(new Runnable() { public void run() { while (num.value <= 1000) { try { lock.lock(); System.out.println("t1等待"); if(num.value != 1) reachOneCondition.await(); if(num.value > 1000) break; if (num.value % 3 == 1) { System.out.println("t1处理:" + num.value); num.value++; } reachTwoCondition.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } } }); Thread t2 = new Thread(new Runnable() { public void run() { while (num.value <= 1000) { try { lock.lock(); System.out.println("t2等待"); reachTwoCondition.await(); if(num.value > 1000) break; if (num.value % 3 == 2) { System.out.println("t2处理:" + num.value); num.value++; } reachThreeCondition.signal(); }catch (Exception e){ e.printStackTrace(); } finally { lock.unlock(); } } } }); Thread t3 = new Thread(new Runnable() { public void run() { while (num.value <= 1000) { try { lock.lock(); System.out.println("t3等待"); reachThreeCondition.await(); if(num.value > 1000) break; if (num.value % 3 == 0) { System.out.println("t3处理:" + num.value); num.value++; } reachOneCondition.signal(); }catch (Exception e){ e.printStackTrace(); } finally { lock.unlock(); } } } }); t3.start(); t2.start(); t1.start(); } }确实不大好看呀,这个代码也是受到一个博文的启发,一开始我的运行结果是这样的
t1等待
t1处理:1
t1等待
t2等待
t3等待
死锁,为什么会发生这种情况,因为在t1中执行reachTwoCondition.signal();的时候t2中的reachTwoCondition还没进入等待状态,我学习的那篇博文的博主教导我们把线程的顺序倒过来start不就行了么,一开始我觉得挺有道理的,也像他那样做了,运行一下,也挺成功的,后来有运行了几次,发现成功到1000是个概率事件,经常会在500多呀800多呀甚至30多发生死锁。为什么,因为循环一定的次数之后总会有概率发生之前这样t1中执行reachTwoCondition.signal();的时候t2中的reachTwoCondition还没进入等待状态,t2t3同样状态的情况,这就很尴尬了,我目前的解决办法是
<span style="white-space:pre"> </span>reachTwoCondition.await(); Thread.sleep(10);//每个线程中加了这么一句 if(num.value > 1000) break;即在等待被唤醒后先等上一小段时间,但这确实不是一个非常好的办法,我再继续想办法
相关文章推荐
- 多线程实战(二)——使用Lock&&Condition对线程进行循环调用
- 多线程间的协调实战——多线程轮流处理
- 多线程编程 实战篇 (一)
- 从COM组件调用.NET组件编程实战
- 多线程应用程序中调用窗体的心得(zz至UP TO YOU的blog)
- 衔接UI线程和管理后台工作线程的类(多线程、异步调用)
- 在多线程环境下使用HttpWebRequest或者调用Web Service
- 可供多线程调用的只能有一个在执行的原子操作实现
- 摘录:MFC界面包装类(多线程时成员函数调用的断言失败)
- 在多线程环境中调用native方法
- 多线程编程 实战篇 (三)
- 在多线程环境中调用native方法[原]
- 简单直观-实战体会Java多线程编程的精要 (5)
- 多线程应用程序中调用窗体的一点心得
- 多线程应用程序中调用窗体的一点心得(摘)
- 简单直观-实战体会Java多线程编程的精要
- 在多线程中调用WinForm
- 简单直观-实战体会Java多线程编程的精要 (4)
- c#多线程编程实例实战
- 实战在Jboss环境下Web Service调用EJB