一道多线程笔试题目的联想

这篇博客介绍了一道Java多线程笔试题,涉及如何将10000个数按奇偶分配到5个文件中。博主分析了错误的线程调度方法,如sleep、设置优先级、让步和合并,并指出这些方法无法精确控制线程执行顺序。正确的解决方案是利用锁和wait/notify机制,确保两个线程交替写入。博主给出了简化版的代码示例,通过使用不同对象锁实现5组线程的同步操作。

题目描述
一个文件中有10000个数,用Java实现一个多线程程序将这个10000个数输出到5个不用文件中(不要求输出到每个文件中的数量相同)。要求启动10个线程,两两一组,分为5组。每组两个线程分别将文件中的奇数和偶数输出到该组对应的一个文件中,需要偶数线程每打印10个偶数以后,就将奇数线程打印10个奇数,如此交替进行。同时需要记录输出进度,每完成1000个数就在控制台中打印当前完成数量,并在所有线程结束后,在控制台打印”Done”.

思路
首先,抓住题目的重点,就是如何让两个线程(奇偶)交替干活。我们学过的线程调度的方法:(1)sleep/interrupt,为了实现两个奇偶线程交替写入,我们必须给两个线程使用同一个对象锁,但是线程sleep之后,只是让出CPU资源,但并不释放对象锁,也就是在一个线程休眠之后,另一个线程还是无法写入。如果不加锁,线程休眠无法准确保证线程的调用顺序。(2)设置线程优先级,与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。 (3)让步。线程的让步含义就是使当前运行着线程让出CPU资源,但是然给谁不知道,仅仅是让出,线程状态回到可运行状态。可能让步完,自己线程接着进入运行状态。(4)合并。线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。 也就是说如果两个线程互相调用对方的join方法来实现交替是什么结果呢?看测试结果:

//偶数线程
public void run() {
        while (step <= 50) {
            synchronized (readNum) {
                try {
                    readEvenNum(br, bw);
                    step++;
                    System.out.println("偶数读了10个,让给奇数。。。。"+step);
                    oddThread.join();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

//奇数线程
public void run() {
        while (step <= 50) {
            synchronized (readNum) {
                try {
                    readOddNum(br, bw);
                    step++;
                    System.out.println("奇数读了10个,让给偶数。。。。"+step);
                    evenThread.join();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

运行结果是:
这里写图片描述
这里写图片描述

结果是某个线程运行一次之后就死锁了。
总结上面错误的思路之后,就是应了那句老话:不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。

个人解题思路
到这里,思路差不多就出来了,使用锁+wait/notify机制。

syn(obj){
    obj.read();
    obj.notify();
    obj.wait();
}

(1)为了保证两个线程能交替干活,给两个线程加上同一个对象锁,这样即使一个线程写数据写到一半退出CPU,但是我还是继续持有该对象锁,另一线程也不能执行写入操作,进而保证了可以交替写入。(2)wait/notify可以保证一个线程在完成写入操作之后,唤醒该对象锁上等待的线程(也就是对面那个线程),同时让自己线程在 该对象监视器上进入等待状态,知道对面线程将其唤醒。
注:wait和sleep区别是,两者都会暂停当前线程,释放CPU控制权,但是wait还会释放对象锁的控制;还要注意的是:notify()调用之后,并不会立即释放对象锁,而是在相应的synchronized(){}语句执行完后,自动释放锁,JVM会在wait对象锁的线程中随机选取一个线程,赋予其对象锁,唤醒线程,继续执行。
代码演示(为了方便调试,将题目的数量级降到原来的1/10):

package wangyi;

import java.io.*;

public class ReadNum {
    public static void main(String[] args) throws Exception {
        File file = new File("D:\\readNum\\num.txt");
        File file1 = new File("D:\\readNum\\out1\\out1.txt");
        ReadNum readNum = new ReadNum();
        EvenThread evenThread = new EvenThread(file, file1, readNum);
        OddThread oddThread = new OddThread(file, file1, readNum);
        evenThread.start();
        oddThread.start();
    }

}

class EvenThread extends Thread {
    FileReader fis;
    FileWriter fos;
    BufferedReader br;
    BufferedWriter bw;
    ReadNum readNum;
    int step = 0;

    public EvenThread(File file, File file1, ReadNum readNum) throws Exception {
        this.fis = new FileReader(file);
        this.fos = new FileWriter(file1,true);
        this.br = new BufferedReader(fis);
        this.bw = new BufferedWriter(fos);
        this.readNum = readNum;
    }
    public void run() {
        while (step <= 50) {
            synchronized (readNum) {
                try {
                    readEvenNum(br, bw);
                    step++;
                    readNum.notify(); 
                    readNum.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void readEvenNum(BufferedReader br, BufferedWriter bw)
            throws IOException {
        int count = 0;
        while (count < 10) {
            String str = br.readLine();
            if(str==null){
                System.out.print(str+"  ");
            }else{
                if (Integer.parseInt(str) % 2 == 0) {
                    bw.write(str);
                    bw.write(" ");
                    bw.flush();
                    count++;
                }           
            }
        }
    }
}

class OddThread extends Thread {
    FileReader fis;
    FileWriter fos;
    BufferedReader br;
    BufferedWriter bw;
    ReadNum readNum;
    int step = 0;

    public OddThread(File file, File file1, ReadNum readNum) throws Exception {
        this.fis = new FileReader(file);
        this.fos = new FileWriter(file1,true);
        this.br = new BufferedReader(fis);
        this.bw = new BufferedWriter(fos);
        this.readNum = readNum;
    }

    public void run() {
        while (step <= 50) {
            synchronized (readNum) {
                try {
                    readOddNum(br, bw);
                    step++;
                    if (step % 5 == 0) {
                        System.out.println("已完成打印数量:" + step * 20);
                        if (step == 50) {
                            System.out.println("Done!");
                        }
                    }
                    readNum.notify();
                    readNum.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void readOddNum(BufferedReader br, BufferedWriter bw)
            throws IOException {
        int count = 0;
        while (count < 10) {
            String str = br.readLine();
            if(str==null){
                System.out.print(str+"  ");
            }else{
                if (Integer.parseInt(str) % 2 == 1) {
                    bw.write(str);
                    bw.write(" ");
                    bw.flush();
                    count++;
                }           
            }
        }
    }
}

这里写图片描述

这里写图片描述

至于题目要求的五组线程,只需要使用五个不同的对象锁即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值