Java 多线程 之常见练习题整理

题是从网上搜的,便于整理,长期更新。

问题一:如何同时处理多条日志打印

现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象。原始代码如下:

交给四个线程,打印16个日志对象信息,启动四个线程容易,但是怎样将这16个日志对象交给4个线程,这时候我们用阻塞队列,将要打印的日志信息放到阻塞队列中,四个线程启动时都从阻塞队列中取数据,取完后打印即可。阻塞队列的大小可以自行设置。

修改为多线程打印日志信息后的代码及注释参考下文:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 多详情并行打印日志
 *
 * @author Soinice
 * @date 2019/5/24 16:35
 */
public class ThreadTestLog {

    /**
     * 模拟处理16行日志 每秒打印一条 开启4个线程 4秒打印完成
     *
     * @param args
     * @return void
     * @author Soinice
     * @date 2019/5/24 17:32
     */
    public static void main(String[] args) {
        System.out.println("begin:" + (System.currentTimeMillis() / 1000));

        final BlockingQueue<String> queue = new ArrayBlockingQueue<>(16);

        // 启动4个线程
        for (int i = 0; i < 4; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 无限从堵塞队列中取出数据并打印
                    while (true) {
                        String log;
                        try {
                            log = queue.take();
                            parseLog(log);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }

        // 循环16次,打印16个目标对象
        for (int i = 0; i < 16; i++) {
            final String log = "" + (i + 1);
            try {
                // 将日志信息放入阻塞队列
                queue.put(log);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解析日志 每一秒输出一条日志
     *
     * @param log
     * @return void
     * @author Soinice
     * @date 2019/5/24 17:41
     */
    private static void parseLog(String log) {
        System.out.println(log + ":" + (System.currentTimeMillis() / 1000));
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

问题二:如何用多线程实现简单的消费者生产者模式

现成程序中的Test类中的代码在不断地产生数据,然后交给BeforeRun.doSome()方法去处理,就好像生产者在不断地产生数据,消费者在不断消费数据。请将程序改造成有10个线程来消费生成者产生的数据,这些消费者都调用BeforeRun.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有顺序的。原始代码如下:

/**
 * 多线程消费
 *
 * @author Soinice
 * @date 2019/5/29 10:34
 */
public class ThreadConsumerTest {

    public static void main(String[] args) {
        before();
    }

    /**
     * 处理之前
     *
     * @param
     * @return void
     * @author Soinice
     * @date 2019/5/29 12:04
     */
    private static void before() {
        System.out.println("begin:" + (System.currentTimeMillis() / 1000));
        for (int i = 0; i < 10; i++) {
            // 用于生产
            String input = i + "";
            // 调用消费方法
            String output = BeforeRun.doSome(input);
            System.out.println(Thread.currentThread().getName() + ":" + output);
        }
    }

    /**
     * 消费者在不断消费数据
     *
     * @author Soinice
     * @date 2019/5/29 12:04
     */
    static class BeforeRun {
        public static String doSome(String input) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String output = input + ":" + (System.currentTimeMillis() / 1000);
            return output;
        }
    }
}

10个线程,依次消费数据,仍然是将待消费数据放入阻塞队列,让10个线程去取数据消费,所不同的是这次消费必须一个一个线程来,而不再是10个线程一起去取,因此用到了线程的同步。同步方法有多种,这里使用Semaphore来进行线程间的同步,代码及注释如下:

  public static void main(String[] args) {
//        before();
        after();

    }

    /**
     * 处理之后
     *
     * @param
     * @return void
     * @author Soinice
     * @date 2019/5/29 14:06
     */
    private static void after() {
        final SynchronousQueue<String> queue = new SynchronousQueue<String>();
        final Semaphore semaphore = new Semaphore(1);

        // 10个线程,分别消费数据,依旧是从阻塞队列中获取数据
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();
                        String input = queue.take();
                        String output = Run.doSome(input);
                        System.out.println(Thread.currentThread().getName() + ":" + output);
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

        System.out.println("begin:" + (System.currentTimeMillis() / 1000));
        for (int i1 = 0; i1 < 10; i1++) {
            String input = i1 + "";
            try {
                queue.put(input);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

执行结果:

问题三:编写程序实现,子线程循环3次,接着主线程循环5次,接着再子线程循环3次,主线程循环5次,如此反复,循环3次。

第一种实现方式:使用synchronized关键字

/**
     * 第一种实现:使用Synchronized关键字
     *
     * @param
     * @return void
     * @author lvyimeng
     * @date 2019/5/30 10:54
     */
    private static void methodOne() {

        final ThreadMethodOne threadMethodOne = new ThreadMethodOne();

        // 调用子线程循环3次
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    threadMethodOne.subThread();
                }
            }
        }).start();

        // 调用主线程循环3次
        for (int i = 0; i < 3; i++) {
            threadMethodOne.mainThread();
        }
    }

    /**
     * 编写功能类,实现主子线程功能
     *
     * @author lvyimeng
     * @date 2019/5/30 10:59
     */
    static class ThreadMethodOne {

        private boolean flag = false;

        /**
         * 主线程
         *
         * @param
         * @return void
         * @author lvyimeng
         * @date 2019/5/30 11:03
         */
        public synchronized void mainThread() {
            while (!flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 主线程循环5次
            for (int i = 0; i < 5; i++) {
                System.out.println("mainThread" + i);
            }

            this.notify();
            flag = false;
        }

        /**
         * 子线程
         *
         * @param
         * @return void
         * @author lvyimeng
         * @date 2019/5/30 14:42
         */
        public synchronized void subThread() {
            while (flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 子线程循环3次
            for (int i = 0; i < 3; i++) {
                System.out.println("subThread" + i);
            }

            this.notify();
            flag = true;
        }

    }

执行结果:

第二种实现方式:使用 lock 锁和 Condition 接口

 /**
     * 第二种实现:使用 lock 锁和 Condition 接口
     *
     * @param
     * @return void
     * @author lvyimeng
     * @date 2019/5/30 17:39
     */
    private static void methodTwo() {

        final ThreadMethodTwo threadMethodTwo = new ThreadMethodTwo();

        // 调用子线程循环3次
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    threadMethodTwo.subThread();
                }
            }
        }).start();

        // 调用主线程循环3次
        for (int i = 0; i < 3; i++) {
            threadMethodTwo.mainThread();
        }
    }

    /**
     * 编写功能类,实现子线程和主线程的功
     *
     * @author lvyimeng
     * @date 2019/5/30 17:42
     */
    static class ThreadMethodTwo extends Thread {

        private boolean flag = false;
        Lock lock = new ReentrantLock();
        Condition con = lock.newCondition();

        /**
         * 主线程
         *
         * @param
         * @return void
         * @author lvyimeng
         * @date 2019/5/30 17:46
         */
        public void mainThread() {
            System.out.println("1:主线程开始 ---- flag=" + flag);
            lock.lock();
            try {
                while (!flag) {
                    System.out.println("2:主线程等待 ---- flag=" + flag);
                    try {
                        // 使当前线程加入 await() 等待队列中,
                        // 并释放当前锁,当其他线程调用signal()会重新请求锁。与Object.wait()类似
                        con.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("7.主线程开始循环5次 ---- flag=" + flag);
                    for (int i = 0; i < 5; i++) {
                        System.out.println("mainThread()" + i + " ---- flag=" + flag);
                    }
                    flag = false;
                    System.out.println("8.唤醒子线程 ---- flag=" + flag);
                    // 唤醒一个在 await() 等待队列中的线程,与Object.notify()相似
                    con.signal();
                }
            } finally {
                lock.unlock();
            }

        }

        /**
         * 子线程
         *
         * @param
         * @return void
         * @author lvyimeng
         * @date 2019/5/30 17:57
         */
        public void subThread() {
            System.out.println("3.子线程开始 ---- flag=" + flag);
            lock.lock();
            try {
                while (flag) {
                    System.out.println("6.子线程等待 ---- flag=" + flag);
                    try {
                        con.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println("4.子线程开始循环3次 ---- flag=" + flag);
                for (int i = 0; i < 3; i++) {
                    System.out.println("subThread()" + i + " ---- flag=" + flag);
                }
                flag = true;
                System.out.println("5.唤醒主线程 ---- flag=" + flag);
                con.signal();
            } finally {
                lock.unlock();
            }
        }
    }

执行结果:

Lock和synchronized的选择

  总结来说,Lock和synchronized有以下几点不同:

  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  5)Lock可以提高多个线程进行读操作的效率。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

问题四:设计四个线程,其中两个线程每次对变量i加1,另外两个线程每次对i减1。

package cn.com.esgcc.gwtrip.hotel.service.biz;

/**
 * 设计四个线程
 * 其中两个线程每次对变量i加1,另外两个线程每次对i减1
 *
 * @author Soinice
 * @date 2019/5/30 18:17
 */
public class ThreadCount {

    public static void main(String[] args) {
        final ThreadMethod threadMethod = new ThreadMethod();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                threadMethod.add();
            }
        });

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadMethod.add();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadMethod.sub();
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadMethod.sub();
            }
        });

        thread.start();
        thread1.start();
        thread2.start();
        thread3.start();
    }

    static class ThreadMethod {

        private int i = 0;

        /**
         * 对变量 i加1
         */
        public synchronized void add() {
            i++;
            System.out.println(Thread.currentThread().getName() + " ---- add():i = " + i);
        }

        /**
         * 对变量 i减1
         */
        public synchronized void sub() {
            i--;
            System.out.println(Thread.currentThread().getName() + " ---- sub():i = " + i);
        }
    }
}

 

  • 11
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 面试题整理是为了帮助准备面试的候选人更好地了解面试的内容和要求。对于Java2021的面试题整理,可以从各个方面进行组织和分类,以便更好地帮助面试者准备。下面是我对Java2021面试题整理的一些建议。 1.基础知识:面试题可以包括Java语言的基本语法、关键字、数据类型、流程控制语句、异常处理等方面的问题。这些问题可以帮助面试者检验自己对Java语言基础知识的掌握程度。 2.面向对象:面试题可以涉及Java面向对象的概念、封装、继承、多态以及接口、抽象类等方面的问题。这些问题可以帮助面试者了解Java面向对象编程的特点和应用。 3.集合框架:面试题可以包括关于Java集合框架的知识,如ArrayList、LinkedList、HashSet、HashMap等的特性、用法和区别。这些问题可以帮助面试者检验自己对Java集合框架的理解和应用能力。 4.多线程:面试题可以涉及Java多线程编程的基本概念、线程的创建与启动、线程同步与互斥、线程池等方面的问题。这些问题可以帮助面试者了解多线程编程的原理和实践。 5.IO流:面试题可以包括关于Java IO流的知识,如输入输出流的分类、字符流和字节流的区别、文件读写操作等方面的问题。这些问题可以帮助面试者检验自己对IO流的理解和应用。 6.异常处理:面试题可以涉及Java异常处理的机制、try-catch语句的使用、自定义异常等方面的问题。这些问题可以帮助面试者了解异常处理的原理和常见应用。 7.Java虚拟机:面试题可以包括Java虚拟机(JVM)的基本概念、内存模型、垃圾回收算法等方面的问题。这些问题可以帮助面试者了解JVM的工作原理和性能优化。 8.框架和工具:面试题可以涉及Java常用的开发框架和工具,如Spring、Hibernate、MyBatis、Maven等方面的问题。这些问题可以帮助面试者了解开发框架的应用和工具的使用。 通过对这些方面的面试题整理,可以帮助面试者全面了解Java2021面试的内容和要求,并有针对性地准备和复习相关知识。面试者应该注重理论的学习,同时结合实践经验进行练习,以便在面试时能够更好地展示自己的能力和潜力。同时,面试者还应注意自己的沟通能力、问题分析能力和解决问题的能力,这些都是面试过程中重要的评估指标。 ### 回答2: Java2021面试题整理要集中在以下几个方面: 1. 基础知识:Java中的基本数据类型、变量和常量、运算符、控制语句等内容是面试中常见的考点。面试官会通过这些问题判断候选人对Java基础知识的熟悉程度和掌握能力。 2. 面向对象编程Java是一门面向对象的编程语言,所以面试中对面向对象的理解和应用也是重要的考点。常见的问题包括类和对象、继承和多态、封装和抽象等。 3. 异常处理:Java中的异常处理是编程中的重要内容,面试中会涉及到异常的概念、异常的分类、如何捕获和处理异常、自定义异常等。 4. 集合框架:Java集合框架是Java开发中常用的工具,常见的面试题会涉及到ArrayList、LinkedList、HashMap等集合的特点和应用场景,以及集合的遍历和使用方法。 5. 多线程Java是一门支持多线程的语言,所以多线程的知识也是面试中的热点考点。常见的问题包括线程的生命周期、线程同步与互斥、线程间的通信、线程池等。 6. JVM相关知识:Java虚拟机(JVM)是Java运行的基础,所以对JVM的了解也是面试中的重要考点。常见问题包括JVM的结构、内存模型、垃圾回收机制等。 此外,面试中还可能涉及到数据库、网络编程、设计模式等其他相关知识。因此,面试前需要对Java的相关知识有全面的掌握,并且要能够灵活运用这些知识进行问题的解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值