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