您的位置:首页 > 编程语言 > Java开发

多线程实战(一)——多线程轮流调用

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,用来使下一个线程进行等待和唤醒状态转换。

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;
即在等待被唤醒后先等上一小段时间,但这确实不是一个非常好的办法,我再继续想办法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息