一位前辈发给我的原题为:
评测题目: 2个线程,一个线程输出1-100这个范围内的所有奇数,一个输出1-100内所有的偶数。要求这些数据最终按照 1.2.3.4....48,49,51,50,53,52,55,54,.....98,97,100,99 这个顺序输出。
解
不是说自己擅自修改题目:
1-49是按顺序打印,有规律可言
51,50,..这里是奇数在前,偶数在后。但是后面变成了偶数在前奇数在后了。最后一部分不知道从哪里开始没有规律的。
分析:
看到题目,想到是利用多线程之间的通信,然后想到这两个线程需要知道对方在什么位置了。
Java实现线程的方式大体上有两种:
- 继承Thread,每个线程对象变量私有
- 实现Runable, 线程之间可以共享变量,自由度更高。
个人选择了Runnable。
线程间的通信:
- 共享变量,想到利用Java集合类Queue接口的实现。本质上也是基于共享变量。
- 线程间wait,notify,Condition的await与signal。
思路核心:每一个线程都知道另外一个线程在做什么,知道它到什么位置了。
代码
- 使用锁是为了保证一次只有一个线程可以打印数。以防出现奇怪的问题。
- numberA和nuumberB都添加了volatile关键字,每次都读取到最新的数据。虽然线程少基本不出现问题,但是以防万一。
- 线程A打印numberA,线程B打印numberB,间隔为2
1-49时:
依次打印:
A发现B比自己往前一个数,打印A,并且加2. 否则不处理
B发现A比自己超前一个数,打印B,并且加2,否则不处理
跳出来的时候:A=51,B=50,为什么会这样呢?
锁会慢一些,当A=49,B=48的时候,两个都进入了循环,但是同一时刻只有B一个能进行打印与加2操作,然后A获得锁,打印,跳出循环。
50~100时:
A = 51,B = 50
A比B大1个数的时候,打印A,跟随上一步比较方便,A加2,否则不处理。
A比B大3个数的时候,打印B,然后B加2,否则不处理。
当B打印98的时候,A=101。B加2为100,再次打印B。
public class PrintNumber {
// 一个线程只打印奇数 1-100, A线程
// 一个线程只打印偶数 1-100, B线程
// 前1-49个数 A在前,B在后
// 50~100 B在后,A在前
//分析:线程之间通信,A知道B,B知道A。
public static void main(String[] args) {
NumberVariable numberVariable = new NumberVariable(1, 2);
Thread a = new Thread(numberVariable, "A");
Thread b = new Thread(numberVariable, "B");
a.start();
b.start();
}
static class NumberVariable implements Runnable {
/**
* 锁对象,只有持有锁的线程才能打印数字。
*/
final Object lock = new Object();
volatile int numberA;
volatile int numberB;
public NumberVariable(int numberA, int numberB) {
this.numberA = numberA;
this.numberB = numberB;
}
/**
* 版本1:
* 分为两个循环,加锁是为了保证同一时刻只有一个线程在打印数字,方便个人理解。
* 循环1:打印 1 2 3 4 5 ...48, 49,顺序打印
* A与B只能保证间隔为1,设置初始变量的时候已经设置间隔为1了。
* 如果:B先进来,B大于A不会打印
* 如果:A先进来,A刚好小于B,A打印,然后A变为3。 接下来B可以打印。然后B变为4,重复刚才的步骤。
* 当B为46的时候,A只能为47,A变为49。然后B变为48。
* <p>
* 循环1跳出去的结果为A:51,B50。
* <p>
* 循环2:
* 1. 假设A先进来,A = 49,B = 50. 然后打印A,A = 53, B = 50,A保持不打印的状态
* 2. 假设B先进来,A = 49,B = 50, B不打印,如果B比A小3,那么已经打印过A的51值,打印50,B = 52
* ...
* A = 99 , B = 98才会打印A的值99, 此时A = 101
* A = 101, B = 98打印B98, B = 100
*/
public void version1() {
while (numberA <= 49 && numberB <= 48) {
//保证只有一个线程在打印数字。
synchronized (lock) {
if ("A".equals(Thread.currentThread().getName())) {
//先判断再打印数字,在A数字比B数字大1的时候才打印
if ((numberB - numberA) == 1) {
System.out.println("当前线程:" + Thread.currentThread().getName() + numberA);
numberA = numberA + 2;
}
} else if ("B".equals(Thread.currentThread().getName())) {
if ((numberA - numberB) == 1) {
System.out.println("当前线程:" + Thread.currentThread().getName() + numberB);
numberB = numberB + 2;
}
}
}
}
while (numberA <= 101 && numberB <= 100) {
//保证只有一个线程在打印数字。
synchronized (lock) {
if ("A".equals(Thread.currentThread().getName())) {
//先判断再打印数字,在A数字比B数字大1的时候才打印
if (numberA - numberB == 1 && numberA != 101) {
System.out.println("当前线程:" + Thread.currentThread().getName() + numberA);
numberA = numberA + 2;
}
} else if ("B".equals(Thread.currentThread().getName())) {
if ((numberA - numberB) == 3) {
System.out.println("当前线程:" + Thread.currentThread().getName() + numberB);
numberB = numberB + 2;
if (numberB == 100) {
System.out.println("当前线程:" + Thread.currentThread().getName() + numberB);
numberB = numberB + 2;
}
}
}
}
}
}
/**
* 考虑使用wait方式。
*/
public void run() {
version1();
}
}
}
额外(非解题)
想着使用集合类,wait,notify来写的,但是写的时候好像用不到,两者之间已经相互知道了,再去用wait,notify通信有些多余。
另外一种方式:
- 队列,保证队列的有序,1,2,3,4,然后才能打印5.
- 奇数打印之后wait,然后通知偶数线程打印。偶数线程打印之后wait通知奇数线程打印。并且把数推入队列
简化一下思路:只按顺序打印,不分成两部分了。想到的伪代码
odd:
while(currentEvenNumber < 101)
if queue.last % 2 == 0,queue.last = (currentEven - 1)
print currentEvenNumber && queue.last = currentEvenNumber;
currentEvenNumber = currentEvenNumber + 2;
oddCondition.wait();
evenCondition.signal();
even:
while(currentOddNumber < 100)
if queue.last % 2 == 1,queue.last = (currentOdd - 1)
print currentOddNumber && queue.last = currentOddNumber;
currentOddNumber = currentOddNumber + 2;
evenCondition.wait();
oddCondition.signal();
既然伪代码都写了,真实的代码也干一下吧。
代码比较粗糙,这个肚子饿了,简答实现一下,非题目。
public class PrintNumer2 {
private static ReentrantLock lock = new ReentrantLock();
private static Condition evendition = lock.newCondition();
private static Condition odddition = lock.newCondition();
private static LinkedList<Integer> integers = new LinkedList<Integer>();
public static void main(String[] args) {
integers.add(0);
new ThreadA().start();
new ThreadB().start();
}
static class ThreadA extends Thread{
private int num = 1;
@Override
public void run() {
super.run();
while (num < 101){
if (integers.getLast() % 2 == 0 && integers.getLast() == (num - 1)){
lock.lock();
System.out.println(Thread.currentThread().getName() + ":" + num);
integers.add(num);
num += 2;
evendition.signal();
try {
odddition.await();
} catch (InterruptedException e) {
// e.printStackTrace();
}
lock.unlock();
}
}
lock.lock();
evendition.signal();
lock.unlock();
}
}
static class ThreadB extends Thread{
private int num = 2;
@Override
public void run() {
super.run();
while (num < 101){
if (integers.getLast() % 2 == 1 && integers.getLast() == (num - 1)){
lock.lock();
System.out.println(Thread.currentThread().getName() + ":" + num);
integers.add(num);
num += 2;
odddition.signal();
try {
evendition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}
lock.lock();
odddition.signal();
lock.unlock();
}
}
}
正常运行
最后
修改了一点点题目,勉强实现了要求,更好的解法欢迎留言。
共同学习,写下你的评论
评论加载中...
作者其他优质文章