java手写题汇总(建议收藏)

1.创建线程

共有四种方式可以创建线程,分别是:继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程

详细创建方式参考下面代码:

继承Thread类

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("MyThread...run...");
    }

    
    public static void main(String[] args) {

        // 创建MyThread对象
        MyThread t1 = new MyThread() ;
        MyThread t2 = new MyThread() ;

        // 调用start方法启动线程
        t1.start();
        t2.start();

    }
    
}

实现runnable接口

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("MyRunnable...run...");
    }

    public static void main(String[] args) {

        // 创建MyRunnable对象
        MyRunnable mr = new MyRunnable() ;

        // 创建Thread对象
        Thread t1 = new Thread(mr) ;
        Thread t2 = new Thread(mr) ;

        // 调用start方法启动线程
        t1.start();
        t2.start();

    }

}

实现Callable接口

public class MyCallable implements Callable<String> {
//MyCallable类实现了Callable<String>接口,该接口是一个泛型接口,指定了call()方法的返回类型为String
    @Override
    public String call() throws Exception {//call方法可以抛出异常
        System.out.println("MyCallable...call...");
        return "OK";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 创建MyCallable对象
        MyCallable mc = new MyCallable() ;

        //创建FutureTask<String>对象ft,并将mc作为参数传入构造方法,用于封装可调用对象
        FutureTask<String> ft = new FutureTask<String>(mc) ;
        //通过将Callable对象封装在FutureTask中,可以在多线程环境下执行任务,并获取任务执行结果。

        // 创建Thread对象
        Thread t1 = new Thread(ft) ;
        Thread t2 = new Thread(ft) ;

        // 调用start方法启动线程
        t1.start();

        //调用ft.get()方法获取执行结果,此方法会阻塞当前线程直到结果返回。
        String result = ft.get();

        // 输出
        System.out.println(result);

    }
}
//输出结果
MyCallable...call...
OK

线程池创建线程

public class MyExecutors implements Runnable{

    @Override
    public void run() {
        System.out.println("MyRunnable...run...");
    }

    public static void main(String[] args) {

        // 创建线程池对象。获取ExecutorService实例,生产禁用,需要手动创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        //通过threadPool.submit(new MyExecutors())来向线程池提交任务。
        threadPool.submit(new MyExecutors()) ;

        //调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。
        threadPool.shutdown();

    }

}

2.手写线程池

public class ThreadPoolExecutorTest {
//它创建了一个线程池,最大线程数为5,核心线程数为2,空闲线程存活时间为2秒,任务队列容量为3。然后向线程池提交了5个任务,每个任务打印当前线程的名称和"ok"。最后关闭线程池。
//跑6条线程,那么6 >= 核心线程数+阻塞队列中的线程数,就会启用其他三条线程中的一个
//跑8条线程,那么8 >= 核心线程数+阻塞队列中的线程数,就会启用其他三条线程中的三个
//跑9条线程,那么9 >= 核心线程数+阻塞队列中的线程数,且9 > 最大线程数(5) + 阻塞队列中的线程数(8),则会触发拒绝策略,这里使用的拒绝策略是AbortPolicy,也就是拒绝处理,并抛出异常
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                2,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
            //当线程池无法接受新任务时,会触发拒绝策略,内置的拒绝策略有四种:
			AbortPolicy:默认策略,直接抛出 RejectedExecutionException 异常。
			CallerRunsPolicy:由调用者线程执行任务。
			DiscardPolicy:默默地丢弃任务,没有任何异常抛出。
			DiscardOldestPolicy:尝试抛弃队列中最旧的任务,然后重新尝试提交当前任务。
        );

        try {
            for (int i = 1; i <= 5 ; i++) {
                threadPool.execute(()-> {
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}
/**
 * pool-1-thread-1ok
 * pool-1-thread-2ok
 * pool-1-thread-2ok
 * pool-1-thread-2ok
 * pool-1-thread-1ok
 */


3. 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?

可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。

代码举例:

为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成

public class JoinTest {

    public static void main(String[] args) {

        // 创建线程对象
        Thread t1 = new Thread(() -> {
            System.out.println("t1");
        }) ;

        Thread t2 = new Thread(() -> {
            try {
                t1.join();                          // 加入线程t1,只有t1线程执行完毕以后,再次执行该线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2");
        }) ;


        Thread t3 = new Thread(() -> {
            try {
                t2.join();                              // 加入线程t2,只有t2线程执行完毕以后,再次执行该线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t3");
        }) ;

        // 启动线程
        t1.start();
        t2.start();
        t3.start();

    }

}

4.synchronized解决抢票超卖问题

如下抢票的代码,如果不加锁,就会出现超卖或者一张票卖给多个人

Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住

public class TicketDemo {

    static Object lock = new Object();
    int ticketNum = 10;


    public synchronized void getTicket() {
        synchronized (this) {
            if (ticketNum <= 0) {
                return;
            }
            System.out.println(Thread.currentThread().getName() + "抢到一张票,剩余:" + ticketNum);
            // 非原子性操作
            ticketNum--;
        }
    }

    public static void main(String[] args) {
        TicketDemo ticketDemo = new TicketDemo();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                ticketDemo.getTicket();
            }).start();
        }
    }


}

5.线程死锁代码

死锁:线程死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。若无外力作用,它们都将无法推进下去。

package com.itheima.basic;

import static java.lang.Thread.sleep;

public class Deadlock {
	//t1 线程获得A资源,接下来想获取B资源
	//t2 线程获得B资源,接下来想获取A资源 
    public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (B) {
                    System.out.println(Thread.currentThread() + "get resource1");
                    
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (B) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (A) {
                    System.out.println(Thread.currentThread() + "get resource1");
                   
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

6.Semaphore信号量

synchronizedReentrantLock 都是一次只允许一个线程访问某个资源,而Semaphore(信号量)可以用来控制同时访问特定资源的线程数量。

以下代码模拟了对某个服务的并发请求,每次只能有 3 个客户端同时访问,请求总数为 10。

public class SemaphoreExample {

    public static void main(String[] args) {
        final int clientCount = 3;
        final int totalRequestCount = 10;
        //意味着最多允许clientCount个线程同时执行
        Semaphore semaphore = new Semaphore(clientCount);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < totalRequestCount; i++) {
            executorService.execute(()->{
                try {
                    //调用semaphore.acquire()方法获取信号量。如果当前信号量的可用资源数量大于0,则会直接获取资源并继续执行;否则,线程将被阻塞,直到有可用资源为止。
                    semaphore.acquire();
                    //在获取资源后,打印出当前信号量的可用资源数量,即semaphore.availablePermits()。
                    System.out.print(semaphore.availablePermits() + " ");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //release()方法释放了一个信号量
                    semaphore.release();
                }
            });
        }
        //调用executorService.shutdown()方法关闭线程池
        executorService.shutdown();
    }

7. CyclicBarrier(同步屏障)

yclicBarrier(同步屏障),用于一组线程互相等待到某个状态,然后这组线程再同时执行。

通过CyclicBarrier实现了一种线程同步机制,即在所有线程都到达某个点之前,它们会一直等待;当所有线程都到达该点后,才会继续执行后续代码。

public class CyclicBarrierExample {

    public static void main(String[] args) {
        final int totalThread = 10;//表示线程总数
        CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < totalThread; i++) {
            executorService.execute(() -> {
                System.out.print("before..");
                try {
                    //调用cyclicBarrier.await()进行等待,直到所有线程都到达屏障点。
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.print("after..");
            });
        }
        //调用executorService.shutdown()关闭线程池。
        executorService.shutdown();
    }
}
//输出结果
before..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after..

7.CountDownLatch倒计时锁

CountDownLatch倒计时锁用于某个线程等待其他线程执行完任务再执行,与thread.join()功能类似。常见的应用场景是开启多个线程同时执行某个任务,等到所有任务执行完再执行特定操作,如汇总统计结果

  • 其中构造参数用来初始化等待计数值
  • await() 用来等待计数归零,调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行;等待timeout时间后count值还没变为0的话就会继续执行
  • countDown() 用来让计数减一

在这个例子中,我们创建了一个CountDownLatch实例,其计数器初始值为3。然后创建了3个线程,每个线程执行一个Worker任务。Worker任务完成后,调用latch.countDown()将计数器减一。主线程通过调用latch.await()等待所有子线程完成任务(即计数器变为0)。当所有子线程完成任务后,主线程继续执行并输出"所有子线程任务已完成"。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        int numberOfThreads = 3;
        CountDownLatch latch = new CountDownLatch(numberOfThreads);

        for (int i = 0; i < numberOfThreads; i++) {
            new Thread(new Worker(latch)).start();
        }

        // 主线程等待所有子线程完成任务
        latch.await();
        System.out.println("所有子线程任务已完成");
    }

    static class Worker implements Runnable {
        private final CountDownLatch latch;

        public Worker(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {
            try {
                // 模拟耗时任务
                Thread.sleep((long) (Math.random() * 1000));
                System.out.println(Thread.currentThread().getName() + " 完成任务");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 完成任务后,计数器减一
                latch.countDown();
            }
        }
    }
}
Thread-2 完成任务
Thread-1 完成任务
Thread-0 完成任务
所有子线程任务已完成

8.重写equals()的例子

在这个示例中,我们创建了一个Person类,包含nameage两个属性。我们重写了hashCode()方法,首先定义一个初始值为17的变量result,然后分别将nameage的哈希值乘以31并加到result上,最后返回result作为对象的哈希码值。这样做的目的是为了让不同的对象尽可能地产生不同的哈希码值,从而提高哈希表等数据结构的性能。

public class Person {
    private String name;
    private int age;

    // 构造函数和其他方法...

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {
        int result = 17; // 初始值
        result = 31 * result + name.hashCode(); // 使用字符串的hashCode()方法计算哈希值
        result = 31 * result + age; // 直接将年龄加入哈希值计算
        return result;
    }
}


9.约瑟夫环问题

约瑟夫环问题的故事背景源自于历史学家约瑟夫斯·弗拉维奥·约瑟夫的《犹太战争》中描述的一个历史事件。在这个问题中,一群人围成一圈,从某个人开始报数,每报到一个特定的数字,那个人就会离开圈子,然后从下一个人开始重新报数,直到最后只剩下一个人。这个问题的目的是找出最后留下的那个人最初的位置。

#include <iostream>
using namespace std;

// 定义链表节点结构体
struct Node {
    int data; // 节点数据
    Node* next; // 指向下一个节点的指针
};

// 创建循环链表
Node* createList(int n) {
    Node* head = new Node(); // 创建头节点
    head->data = 1;
    head->next = NULL;
    Node* temp = head; // 临时指针,用于连接节点
    for (int i = 2; i <= n; i++) {
        Node* newNode = new Node(); // 创建新节点
        newNode->data = i;
        newNode->next = NULL;
  

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java抽象带蓝子的笔记专栏 文章被收录于专栏

我的笔记专栏,内有自己整理的八股知识笔记和算法刷题笔记,我会不断通过他人和自己的面经来更新和完善自己的八股笔记。专栏每增加一篇文章费用就会上涨一点,如果你喜欢的话建议你尽早订阅。内有超详细苍穹外卖话术!后续还会更新其他项目和我的实习经历的话术!敬请期待!

全部评论
byd只收藏不点赞是吧
4 回复 分享
发布于 08-07 21:49 广东
大家努努力给我的专栏点个订阅,祝我早日集齐库里南碎片😋
3 回复 分享
发布于 08-08 10:02 广东
手写线程池那里,应该不是让你直接调用API,而是让你手写一个线程池,可以参考一下我这篇博客:https://blog.csdn.net/YANG_sena/article/details/134905009。 整体质量还可以。就是不知道为什么还有C++的代码。
2 回复 分享
发布于 08-08 11:31 重庆
来来来,大家把专栏订阅走一波
1 回复 分享
发布于 08-07 22:04 广东
感谢分享
1 回复 分享
发布于 08-11 08:32 江苏
大佬
1 回复 分享
发布于 08-14 23:03 黑龙江
并发仙人
点赞 回复 分享
发布于 08-07 21:48 四川
点赞收藏了
点赞 回复 分享
发布于 08-08 07:06 北京
爱了爱了立刻收藏
点赞 回复 分享
发布于 08-08 08:21 四川
跟着篮子,吃饱喝足去**
点赞 回复 分享
发布于 08-08 10:45 四川
mark
点赞 回复 分享
发布于 08-08 10:57 贵州
mark
点赞 回复 分享
发布于 08-08 11:41 江苏
nice
点赞 回复 分享
发布于 08-08 16:31 浙江
mark
点赞 回复 分享
发布于 08-12 09:21 四川
Mark多线程
点赞 回复 分享
发布于 08-12 12:45 北京
这个手写HashMap的put函数,如果添加了两个相同的key(hashCode()与equls()都相同),从效果来看似乎是直接追加到next,而不是替换,这是否有点问题呢
点赞 回复 分享
发布于 08-14 17:54 四川
第四个解决超卖,为什么方法加了 synchorized 之后,还要在里面对对象加锁呢,是不是有些冗余?
点赞 回复 分享
发布于 09-02 22:25 安徽
这些手写的要达到自己都会的程度吗面试会要手写吗
点赞 回复 分享
发布于 11-22 11:23 四川

相关推荐

不愿透露姓名的神秘牛友
11-26 12:52
已编辑
京东方 java 17*14 硕士985
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
11-25 19:42
快手 电商Java后端 24*16 + 2*12 本科其他
点赞 评论 收藏
分享
评论
158
733
分享
牛客网
牛客企业服务