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信号量
synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,而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类,包含name和age两个属性。我们重写了hashCode()方法,首先定义一个初始值为17的变量result,然后分别将name和age的哈希值乘以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%内容,订阅专栏后可继续查看/也可单篇购买
内容包含: 1.八股大全:多一句没有少一句不行的最精简八股整理,完全可以应付校招八股拷打! 2.速成项目话术:目前有魔改苍穹外卖项目话术(额外扩展了很多技术亮点),能速成拿去面试,后面会更新魔改黑马点评、商城项目等等热门高质量项目话术 3.智力题超详细题解汇总; 4.面试时非技术问题话术整理,绝对震惊面试官一年; 5.算法lc hot100全题系列题解:绝对通俗易懂。 会慢慢涨价,欢迎订阅!


查看25道真题和解析