• Java面试编程相关题目


    前言
    面试官通过Java相关的手撕题目能够很好的看出面试者是否真的具备工程思维,如果有项目,那么这一块一定要掌握好,否则项目的可信度会大打折扣。这类题目可以粗略分为两类,与线程相关的考察的是并发相关编程能力,设计模式以及消息队列相关,就更加考验面向对象的思维。其实知识点不多,重点是要滤清有些什么类,各自实现什么功能,如何配合。

    这里基本就是所有需要掌握的了,应该不会少,掌握这些大厂应该都没问题,也不会多,我在面试的过程中全部遇到过。(部分没整理完,这两天会继续整理)

    面试手撕除了这种题,当然还有力扣算法题,我也做了整理,也在陆续更新,还没有写完。

    1. 实现三个线程交替打印1-120

    这个问题考察的是线程之间的通信,线程之间通信的手段可以通过系统提供的线程间通信方法配合锁、信号量等方法实现,方法很多,会两到三个即可。
    方法一:synchronized关键字+wait()+notifyAll()

    public class PrintNumber{
        private int num;
        private static final Object lock = new Object();
        public void print(int order){
            while(true){
                synchronized(lock){
                    if(num >= 10){break;}
                    while(num % 3 == order){
                        try{
                            System.out.println(Thread.currentThread().getName()+num);
                            num++;
                            lock.notifyAll();
                            lock.wait();
                        }catch(Exception e){
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        public static void main(String[]args){
            PrintNumber test = new PrintNumber();
            Thread t0 = new Thread(()->{test.print(0);},"A");
            Thread t1 = new Thread(()->{test.print(1);},"B");
            Thread t2 = new Thread(()->{test.print(2);},"C");
    
            t0.start();
            t1.start();
            t2.start();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    方法二:RentrantLock

    import java.util.concurrent.locks.ReentrantLock;
    
    public class Main{
            private static int num;
            private ReentrantLock lock = new ReentrantLock();
    
            public void print(int order){
                while (true){
                    lock.lock();
                    if(num >= 40){
                        break;
                    }
                    if(num % 3 == order){
                        System.out.println(Thread.currentThread().getName()+num);
                        num++;
                    }
                    lock.unlock();
                }
            }
            public static void main(String[]args){
                    Main test = new Main();
    
                    Thread t0 = new Thread(()->{test.print(0);},"A");
                    Thread t1 = new Thread(()->{test.print(1);},"B");
                    Thread t2 = new Thread(()->{test.print(2);},"C");
    
                    t0.start();
                    t1.start();
                    t2.start();
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    方法三:Semaphore信号量

      2. 实现一个线程池

      定义 Runnable 线程

      import java.util.Date;
      
      /**
       * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。
       * @author shuang.kou
       */
      public class MyRunnable implements Runnable {
      
          private String command;
      
          public MyRunnable(String s) {
              this.command = s;
          }
      
          @Override
          public void run() {
              System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
              processCommand();
              System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
          }
      
          private void processCommand() {
              try {
                  Thread.sleep(5000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      
          @Override
          public String toString() {
              return this.command;
          }
      }
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36

      定义线程池

      import java.util.concurrent.ArrayBlockingQueue;
      import java.util.concurrent.ThreadPoolExecutor;
      import java.util.concurrent.TimeUnit;
      
      public class ThreadPoolExecutorDemo {
      
          private static final int CORE_POOL_SIZE = 5;
          private static final int MAX_POOL_SIZE = 10;
          private static final int QUEUE_CAPACITY = 100;
          private static final Long KEEP_ALIVE_TIME = 1L;
          public static void main(String[] args) {
      
              //使用阿里巴巴推荐的创建线程池的方式
              //通过ThreadPoolExecutor构造函数自定义参数创建
              ThreadPoolExecutor executor = new ThreadPoolExecutor(
                      CORE_POOL_SIZE,
                      MAX_POOL_SIZE,
                      KEEP_ALIVE_TIME,
                      TimeUnit.SECONDS,
                      new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                      new ThreadPoolExecutor.CallerRunsPolicy());
      
              for (int i = 0; i < 10; i++) {
                  //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
                  Runnable worker = new MyRunnable("" + i);
                  //执行Runnable
                  executor.execute(worker);
              }
              //终止线程池
              executor.shutdown();
              while (!executor.isTerminated()) {
              }
              System.out.println("Finished all threads");
          }
      }
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37

      3. 设计模式相关

      1) 实现单例模式

      首先要明白什么是单例模式,简单来说假如有类 A, 当我们 new 一个 A 的对象的时候,如果是单例模式,先判断是否已经有这个对象了,如果有就返回,没有再创建。这相当于将一个对象和一个类绑定了,该怎么做呢?我们知道,在Java中,如果将一个属性或方法声明为 static 的,那么这个属性或方法就是属于这个类的了,与类绑定了。那办法就呼之欲出了,我们在类中声明一个自身的对象,声明为static的,同时不允许做出修改,加上final关键字。

      除此之外,单例模式有两种实现方式,即 饿汉模式 与 懒汉模式。饿汉模式是在类加载的时候直接创建对象,懒汉模式就是当第一次 new 类 A 的对象的时候,才去创建唯一实例。

      从以上的描述不难看出,如果是饿汉模式,我们直接将属性初始化即可,如果是懒汉模式,就需要在构造函数中检查是否已经存在相应的对象了,因为要保证单例,当已经存在的时候就不能够再创建了。除此之外,无论是单例还是多例,都需要为外界提供一个 getInstance 函数来获得对象。

      饿汉模式

      public class Singleton{
      	private static final Singleton instance = new Singleton();
      	private Singleton(){}
      	public static Singleton getInstance(){
      		return instance;
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      懒汉模式(方式一)

      public class Singleton{
      	private static final Singleton instance;
      	private Singleton(){}
      	public static synchronized Singleton getInstance(){
      		if(instance == null){
      			this.instance = new Singleton();
      		}
      		return instance;
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      懒汉模式(方式二:双重校验)

      public class Singleton{
      	private static final Singleton instance;
      	private Singleton(){}
      	public static Singleton getInstance(){
      		if(instance == null){
      			synchronized (Singleton.class){
      				if(instance == null){
      					this.instance = new Singleton();
      				}
      			}
      		}
      		return instance;
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 为什么懒汉模式要加锁?
        因为多线程可能会多次创建,因为要保证单例,所以必须加锁。
      • 为什么要用双重校验?
        因为第一种方式直接在方法上加锁,过于笨重,当创建了实例之后,后面的每一次访问,其实不用创建,但是还是要被加锁,用第二种的对代码块加锁,更加的灵活,更加的符合场景。
      • 为什么双重校验方法里,synchronized 关键字的代码块中还要检验一次?
        同样是为了避免多线程多次创建,如果创建之前不检查,可能别的线程也创建了。
      • 双重校验有什么好处?
        获取对象无需加锁,更加符合场景;创建过程线程安全。

      2)实现适配器模式

      3)实现代理模式

      4) 实现策略模式

      5) 实现生产者-消费者模式

      4. 实现消息队列

    • 相关阅读:
      【C++】map / multimap容器
      基于Java毕业设计政府机关门禁管理系统源码+系统+mysql+lw文档+部署软件
      【Spring Boot 集成应用】Kafka的集成用法
      Java 性能 - ArrayLists 与 Arrays 的大量快速读取
      groovy 语言学习
      QT With OpenGL(泛光)(Bloom)
      一般哪些原因会造成硬盘损坏呢
      3分钟带你了解微信小程序开发
      Go的数据结构-hashmap
      关于二级页表的引入
    • 原文地址:https://blog.csdn.net/buzhidao2333shuosha/article/details/132842024