你应该会的一道多线程笔试题

前言

最近也面了好多家企业,也总结到很多笔试经验和面试经验。笔试大多数Java题目都是牛客网原题和简单排序,数据库,Java基础概念,数据结构,MVC模式等。面试官问的题目涉及的知识无非是Java基础知识,设计模式,网络等。我发现出现频率很高的知识点有多线程,设计模式(单例模式,策略模式,观察者模式)等。今天就来说一下笔试和面试中常见的多线程题目。

dream.jpg

笔试

  • 题目:有ABC三个线程,,A线程输出AB线程输出BC线程输出C,要求,同时启动三个线程,,按顺序输出ABC,循环10次。这道题目出现的频率很高啊。
第一种思路
  • 创建3个线程轮流输出,用lock对象去同步线程的状态,用count变量标识出哪个线程,MAX变量用于边界控制,适时退出轮询。(没有用到wait()和notify()线程通信机制)
  • 手写代码
public class PrintABC {

    public static void main(String[] args) {
        final Lock lock = new ReentrantLock();
        Thread a = new Thread(new PrintfABCThread("A", lock, 0));
        Thread b = new Thread(new PrintfABCThread("B", lock, 1));
        Thread c = new Thread(new PrintfABCThread("C", lock, 2));

        a.start();
        b.start();
        c.start();
    }
}

class PrintfABCThread implements Runnable {
    private String name;
    private Lock lock;
    private Integer flag;

    public static int count = 0;

    public static final int MAX = 30;

    public PrintfABCThread(String name, Lock lock, Integer flag) {
        this.name = name;
        this.lock = lock;
        this.flag = flag;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();

            if (count >= MAX) {
                lock.unlock();
                return;
            }

            if (count % 3 == flag) {
                System.out.println(name);
                count++;
            }
            lock.unlock();
        }
    }
}
  • 输出结果

image.png

第二种思路
  • 通过Thread类的join()方法让我们开启的线程加入到主线程,只有我们开启的新线程结束后,主线程才能继续执行。(不满足题意,创建了30个线程,而且没有同时开启线程)
  • 手写代码
public class PrintfABC {

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread a = new Thread(new PrintThread("A"));
            a.start();
            a.join();
            Thread b = new Thread(new PrintThread("B"));
            b.start();
            b.join();
            Thread c = new Thread(new PrintThread("C"));
            c.start();
            c.join();
        }
    }
}

class PrintThread implements Runnable {
    private String name;

    public PrintThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(name);
    }
}
  • 输出结果

image.png

第三种思路
  • 定义一个MainLock继承于ReentrantLock,里面维护着3个condition,用于线程之间的通信。
public class MainLock extends ReentrantLock {

    private static final long serialVersionUID = 7103258623232795241L;

    private int count = 0;

    private final int max;

    private final Condition a;

    private final Condition b;

    private final Condition c;

    public MainLock(int max) {
        this.max = max;
        this.a = this.newCondition();
        this.b = this.newCondition();
        this.c = this.newCondition();
    }

    public boolean isEnd() {
        if (count >= max) {
            return true;
        }

        return false;
    }

    public void increase() {
        count++;
    }

    public int getCount() {
        return this.count;
    }

    public int getMax() {
        return this.max;
    }

    public Condition getA() {
        return this.a;
    }

    public Condition getB() {
        return this.b;
    }

    public Condition getC() {
        return this.c;
    }

    public boolean isA() {
        return count % 3 == 0;
    }

    public boolean isB() {
        return count % 3 == 1;
    }

    public boolean isC() {
        return count % 3 == 2;
    }
}
  • 创建一个定长线程池,开启3个线程,分别去处理输出A,B,C的请求。
public class Main {

    public static void main(String[] args) {
        MainLock lock = new MainLock(30);
        ExecutorService pool = Executors.newFixedThreadPool(3);
        pool.submit(new AThread(lock));
        pool.submit(new BThread(lock));
        pool.submit(new CThread(lock));

        pool.shutdown();
    }
}

class AThread implements Runnable {
    private final MainLock lock;

    public AThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isA()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getB().signal();
                } else {
                    try {
                        lock.getA().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.println("A ");
    }
}

class BThread implements Runnable {
    private final MainLock lock;

    public BThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isB()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getC().signal();
                } else {
                    try {
                        lock.getB().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.println("B ");
    }
}

class CThread implements Runnable {
    private final MainLock lock;

    public CThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isC()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getA().signal();
                } else {
                    try {
                        lock.getC().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.println("C ");
    }
}
  • 输出结果

image.png


  • 第二个题目: 用多线程去处理"abc""def"“ghi”这个三个字符串,让它们以"adg""beh"“cfi”这种形式输出。这个题目之前是红星美凯龙技术部笔试卷的压轴题,分值是20分。
第一种思路

其实跟第一个题目的解决思路是差不多,唯一变的就是我们要获取下标访问字符串从而获取字符。我们可以通过count变量来标识由哪一个线程输出,通过count / 3 获取下标。(还是没有用到wait()和notify()机制)

public class DemoTwo {

    public static void main(String[] args) {
        final Lock lock = new ReentrantLock();
        Thread a = new Thread(new PrintThread("abc", lock, 0));
        Thread b = new Thread(new PrintThread("def", lock, 1));
        Thread c = new Thread(new PrintThread("ghi", lock, 2));

        a.start();
        b.start();
        c.start();
    }
}

class PrintThread implements Runnable {
    private String name;
    private Lock lock;
    private Integer flag;

    public static int count = 0;

    public static int MAX = 9;

    public PrintThread(String name, Lock lock, Integer flag) {
        this.name = name;
        this.lock = lock;
        this.flag = flag;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();

            if (count >= MAX) {
                lock.unlock();
                return;
            }

            if (count % 3 == flag) {
                System.out.print(name.charAt(count / 3) + " ");
                count++;
            }

            lock.unlock();
        }
    }
}
  • 输出结果。

image.png

第二种思路
  • 和上面的思路是一样的。(没有同时开启3个线程)
  • 手写代码
public class DemoOne {

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            Thread a = new Thread(new MyThread("abc", i));
            a.start();
            a.join();

            Thread b = new Thread(new MyThread("def", i));
            b.start();
            b.join();

            Thread c = new Thread(new MyThread("ghi", i));
            c.start();
            c.join();

            System.out.println("");
        }
    }
}

class MyThread implements Runnable {
    private String str;
    private int index;

    public MyThread(String str, int index) {
        this.str = str;
        this.index = index;
    }

    @Override
    public void run() {
        System.out.print(String.valueOf(str.charAt(index)) + " ");
    }
}
  • 输出结果。

image.png

第三种思路
public class Main3 {

    public static void main(String args[]) {
        MainLock lock = new MainLock(9);
        ExecutorService pool = Executors.newFixedThreadPool(3);
        pool.submit(new XThread(lock));
        pool.submit(new YThread(lock));
        pool.submit(new ZThread(lock));

        pool.shutdown();
    }
}

class XThread implements Runnable {
    private final MainLock lock;

    public XThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isA()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getB().signal();
                } else {
                    try {
                        lock.getA().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.print("abc".charAt(lock.getCount() / 3));
    }

}

class YThread implements Runnable {
    private final MainLock lock;

    public YThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isB()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getC().signal();
                } else {
                    try {
                        lock.getB().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.print("def".charAt(lock.getCount() / 3));
    }

}

class ZThread implements Runnable {
    private final MainLock lock;

    public ZThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isC()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getA().signal();
                } else {
                    try {
                        lock.getC().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.print("ghi".charAt(lock.getCount() / 3));
    }

}
  • 输出结果

image.png


面试

昨天去扫呗面试,面试官问我多线程的实现的二种方式和彼此之间的区别。这个也很简单,百度也烂大街了。

  • 采用extends Thread 方式
    • 优点:编程简单,如果要访问当前线程,无需使用Thread.currentThread()方法,可以直接用this,即可获取当前线程。
    • 缺点:由于继承了Thread,类无法再继承其他的父类。
    • 使用方式:直接new 相应的线程类即可。
  • 采用implements Runnable 方式
    • 优点:没有继承Thread类,所以可以继承其他的父类,在这种形式下,多个线程可以共享同一个对象,所以非常合适多个相同的线程来处理同一份资源的情况下,把cpu代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。适用场景,比如卖票。
    • 缺点:编程稍微复杂,如果要访问当前线程,必须使用Thread.currentThread()方法。
    • 使用方式:不能直接创建所需类的对象并运行它,而是必须从Thread类的一个实例内部启动它。
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

尾言

就算失望不能绝望,明天又去面试,美滋滋。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏微信公众号:Java团长

Java知识点集锦

答:不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primi...

1251
来自专栏北京马哥教育

Python2.x与3​​.x版本区别

? 文 | 豌豆 来源 | 菜鸟教程 Python的3.0版本,常被称为Python 3000,或简称Py3k。相对于Python的早期版本,这是一个较...

3466
来自专栏猿人谷

求子数组的最大和

分析:输入一个整形数组,数组里有正数也有负数,数组中一个或连续的多个正数,求所有子数组的和的最大值。 当我们加上一个正数时,和会增加;当我们加上一个负数时,和会...

19810
来自专栏我爱编程

Day18内建模块collections&base64collectionsbase64

collections collections是Python内建的一个集合模块,提供了许多有用的集合类。 namedtuple >>> from collect...

4258
来自专栏java达人

最有价值的50道java面试题(二)

来自骆昊的技术专栏 26、抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchron...

29210
来自专栏Java3y

LinkedHashMap就这么简单【源码剖析】

1644
来自专栏Java开发者杂谈

java如何获取一个对象的大小

When---什么时候需要知道对象的内存大小 在内存足够用的情况下我们是不需要考虑java中一个对象所占内存大小的。但当一个系统的内存有限,或者某块程序代码允许...

1.3K7
来自专栏ShaoYL

OC语言Block 续

33512
来自专栏calmound

sprintf的用法

在将各种类型的数据构造成字符串时,sprintf 的强大功能很少会让你失望。由于sprintf 跟printf 在用法上几乎一样,只是打印的目的地不同而已,前者...

3466
来自专栏SeanCheney的专栏

Python题目

简述函数式编程 在函数式编程中,函数是基本单位,变量只是一个名称,而不是一个存储单元。除了匿名函数外,Python还使用fliter(),map(),red...

51716

扫码关注云+社区

领取腾讯云代金券