前言
面试官通过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();
}
}
方法二: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();
}
}
方法三: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;
}
}
定义线程池
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");
}
}
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;
}
}
懒汉模式(方式一)文章来源:https://www.toymoban.com/news/detail-732843.html
public class Singleton{
private static final Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
this.instance = new Singleton();
}
return instance;
}
}
懒汉模式(方式二:双重校验)文章来源地址https://www.toymoban.com/news/detail-732843.html
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;
}
}
- 为什么懒汉模式要加锁?
因为多线程可能会多次创建,因为要保证单例,所以必须加锁。 - 为什么要用双重校验?
因为第一种方式直接在方法上加锁,过于笨重,当创建了实例之后,后面的每一次访问,其实不用创建,但是还是要被加锁,用第二种的对代码块加锁,更加的灵活,更加的符合场景。 - 为什么双重校验方法里,synchronized 关键字的代码块中还要检验一次?
同样是为了避免多线程多次创建,如果创建之前不检查,可能别的线程也创建了。 - 双重校验有什么好处?
获取对象无需加锁,更加符合场景;创建过程线程安全。
2)实现适配器模式
3)实现代理模式
4) 实现策略模式
5) 实现生产者-消费者模式
4. 实现消息队列
到了这里,关于Java面试编程手撕相关题目的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!