常见Java面试手撕题

手写单例模式

双重校验锁

class Single {
	private Single(){}
	private volatile static Single single= null;
	public static Single getInstance(){
	if ( single == null ) {
		synchronized (Single.class) {//锁整个对象
		if ( single == null ) {
			single = new Single();
			}
		}
	}
	return single;
	}
}

快排排序

class Solution {
    //快速排序
    public int[] sortArray(int[] nums) {

//需要打乱顺序,不然容易超时
        shffle(nums);
        sortArray(nums, 0 , nums.length-1);
        return nums;
    }
    public void sortArray(int[] nums, int lo, int hi) {

            if(lo >= hi) return;
            int p = partition(nums, lo, hi);
            sortArray(nums, lo, p-1);
            sortArray(nums, p+1, hi);
    }
    //只要第一个数大于当前数字,跳过,直到遇见第一个大于privot的数字
    //只要从最后一个数字开始小于privot,j--
    //i>j 跳出循环
    //交换位置

    public int partition(int[] nums, int lo, int hi) {      
            int privot = nums[lo];

        int i = lo+1;
        int j = hi;
        while(i<= j){
            while(i <= j && nums[i] <= privot){
                i++;
            }
            while(i <= j && nums[j] > privot){
                j--;
            }
            if(i > j){
                break;
            }
            swap(nums,i,j);
        }
        swap(nums, lo, j);
        return j;
}

 private int partition(int[] nums, int low, int hi) {
        int prvot = nums[low];

        int j = low;
        for (int i = low+1; i <= hi; i++) {
            if(prvot > nums[i]){
                //看清楚,先加加再交换
                j++;
                sawp(nums, i, j);
            }
        }
        //最后j一定是第一个节点的值,把它与最后一个位置交换,快排结束
        sawp(nums, low, j);

        return j;
  }


    public void swap(int[] nums, int lo, int hi) {
        int temp = nums[lo] ;
        nums[lo] = nums[hi];
        nums[hi] = temp;
    }
    public void shffle(int[] nums) {
        Random r = new Random();
        int n = nums.length;
        for(int i =0; i<n; i++){
                int j = i+ r.nextInt(n-i);
                swap(nums, i, j);
        }

    }
}

归并排序

int[] cur ;
    //归并排序
    public int[] sortArray(int[] nums) {
        //需要打乱顺序,不然容易超时
        int n= nums.length;
        cur = new int[n];
        sortArray(nums, 0 , nums.length-1);
        return nums;
    }
    public void sortArray(int[] nums, int lo, int hi){
        if(lo >=hi) return;
        int mid = lo + (hi-lo)/2;
        sortArray(nums, lo, mid);
        sortArray(nums, mid+1, hi);
        merge(nums, lo, mid, hi);
    }
    public void merge(int[] nums, int lo, int mid, int hi){
        for(int i = lo; i<=hi; i++){
            cur[i] = nums[i];
        }
        int index = lo;
        int i = lo; int j = mid+1;
        while(i <=mid && j<= hi){
            if(cur[i] < cur[j]){
                nums[index++] = cur[i++];
            }else{
                nums[index++] = cur[j++];
            }
        }
        while( i<= mid) nums[index++] = cur[i++];
        while( j<= hi) nums[index++] = cur[j++];

    }
    public void swap(int[] nums, int lo, int hi) {
        int temp = nums[lo] ;
        nums[lo] = nums[hi];
        nums[hi] = temp;
    }
}

堆排序

class Solution {

    //堆排序
    public int[] sortArray(int[] nums) {
        int len = nums.length;
        // 将数组整理成堆
        heapify(nums);

        // 循环不变量:区间 [0, i] 堆有序
        for (int i = len - 1; i >= 1; ) {
            // 把堆顶元素(当前最大)交换到数组末尾
            swap(nums, 0, i);
            // 逐步减少堆有序的部分
            i--;
            // 下标 0 位置下沉操作,使得区间 [0, i] 堆有序
            siftDown(nums, 0, i);
        }
        return nums;
    }
    /**
     * 将数组整理成堆(堆有序)
     *
     * @param nums
     */
    private void heapify(int[] nums) {
        int len = nums.length;
        // 只需要从 i = (len - 1) / 2 这个位置开始逐层下移
        for (int i = (len - 1) / 2; i >= 0; i--) {
            siftDown(nums, i, len - 1);
        }
    }

    public void siftDown(int[] nums, int k, int end){
        while (2 * k + 1 <= end) {
            int j = 2 * k + 1;
            if (j + 1 <= end && nums[j + 1] > nums[j]) {
                j++;
            }
            if (nums[j] > nums[k]) {
                swap(nums, j, k);
            } else {
                break;
            }
            k = j;
        }
    }
    
    public void swap(int[] nums, int lo, int hi) {
        int temp = nums[lo] ;
        nums[lo] = nums[hi];
        nums[hi] = temp;
    }
}
 

约瑟夫环

方法一数组

import java.util.*;  
  
public class Josephus {  
    public static void main(String[] args) {  
        Scanner sc = new Scanner(System.in);  
        int N, M; 
        //表示总共有n人,数到数字m时出局 
        N = sc.nextInt();  
        M = sc.nextInt(); 
        //元素值为0表示未出局 
        int[] a = new int[110];
        //cnt表示目前出局的人数
        int cnt = 0, i = 0, k = 0;  
        while (cnt != N) {  
            i++;      //i是每个人的编号 
//这里需要特别注意:i的值是不断累加的,一旦发现i的值>N,那么i需要重新从第1个人开始
//数组要从第一个元素重新开始一个一个往后判断 
            if (i > N) i = 1;  
            if (a[i] == 0) {  
                k++;  
                if (k == M) {  
                    a[i] = 1;   //编号为i的这个人出局
                    cnt++;   //出局的人数+1
                    System.out.print(i + " ");  
                    k = 0;  
                }  
            }  
        }  
        sc.close();  
    }  
}

以上的代码中人编号是从1开始的,如果编号从0开始,那么出局的顺序是怎样的呢?代码应该如何修改呢?

第一种方式:如下图所示

System.out.print(i-1 + " ");

二、 循环链表

import java.util.LinkedList;  
  
public class Josephus {  
    public static void main(String[] args) {  
        ysflb(10, 3);  
    }  
  
    // 用链表实现约瑟夫环问题 (循环链表)   
    static class Node {  
        int data;  
        Node next;  
  
        Node(int data) {  
            this.data = data;  
        }  
    }  
  
    static void ysflb(int N, int M) {  
        // 初始化循环链表  
        Node head = null, p = null, r = null;  
        head = new Node(1); // 从1开始编号  
        head.next = null; //一开始整个链表只有一个Node(结点),这个Node有两个域,分别是data和next  
                          //data从1开始,next指向NULL,总共需要N个结点,现在创建了一个,还需要N-1个   
        p = head; //head要保持不能改变,才能够找到链表的起始位置,一开始p也指向第一个结点  
                  //p等一下会被使用,用它可以便于创建剩下的N-1个结点   
  
        // 创建循环链表  
        for (int i = 2; i <= N; i++) {  
            r = new Node(i); // 创建新的结点  
            p.next = r;
            p = r; 
        }  
        p.next = head;     //最后一个结点的next指向头结点
        p = head;          //为后续方便,将p指向头结点
         
  
        // 约瑟夫环的模拟  
        while (p.next != p) { // 如果p的next=p,说明目前只有一个元素   
            for (int i = 1; i < M; i++) { // 报到数字为M的时候出局   
                r = p; // 保留出局的前一个结点   
                p = p.next; // p指向的是要出局的这个结点,需要保留前一个结点  
            }  
            // 输出出局结点的数据  
            System.out.print(p.data + " ");  
            r.next = p.next; // 删除p的结点,此时p指向哪里?   
            p = p.next; // 更新p重新进行报数   
        }   
        System.out.println(p.data); // 输出最后留在环中的结点的数据  
    }  
}

三、递归

import java.util.Scanner;  
  
public class Josephus {  
    // 用递归实现约瑟夫环问题  
    public static int ysfdg(int N, int M, int i) {  
        if (i == 1) {  
            return (M - 1 + N) % N;  
        }  
        return (ysfdg(N - 1, M, i - 1) + M) % N;  
    }  
  
    public static void main(String[] args) {  
        Scanner scanner = new Scanner(System.in);   
        int N = scanner.nextInt();  
        int M = scanner.nextInt();  
        for (int i = 1; i <= N; i++) {  
            System.out.print(ysfdg(N, M, i) + " ");  
        }  
        scanner.close();  
    }  
}

【多线程】详解阻塞队列以及常见的面试题

1. 阻塞队列

阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则,阻塞队列带有阻塞特性,是一种线程安全的数据结构.

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
public static void main(String[] args) throws InterruptedException {
    BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>();
    blockingDeque.put(1);
    blockingDeque.put(2);
    blockingDeque.put(3);
    blockingDeque.put(4);
    System.out.println(blockingDeque.take());
    System.out.println(blockingDeque.take());
    System.out.println(blockingDeque.take());
    System.out.println(blockingDeque.take());
}

阻塞队列一个重要的特性就是拥有阻塞状态,一般的队列,take次数大于put次数的时候就会抛出异常;然而我们的阻塞队列,take次数大于put次数的时候,不会报异常,会进入阻塞状态.如下图运行所示.

2. 生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者消费者这样的模型是针对某个资源来说的.

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.

  • 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.
  • 阻塞队列也能使生产者和消费者之间 解耦.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class Test16 {
public static void main(String[] args) {
    BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

    // 消费者
    Thread t1 = new Thread(() -> {
        while (true) {
            try {
                int value = blockingQueue.take();
                System.out.println("消费元素: " + value);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t1.start();

    // 生产者
    Thread t2 = new Thread(() -> {
        int value = 0;
        while (true) {
            try {
                System.out.println("生产元素: " + value);
                blockingQueue.put(value);
                value++;
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t2.start();
}
}

3. 模拟实现阻塞队列

实现一个队列(循环队列)

添加线程安全(使用 synchronized 进行加锁控制. )

添加阻塞功能

put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定队列就不满了, 因为同时可能是唤醒了多个线程),take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait).

class MyBlockingQueue2 {
    private int[] items = new int[1000];
    volatile private int head = 0;
    volatile private int tail = 0;
    volatile private int size = 0;

    // 入队列
    synchronized public void put(int elem) throws InterruptedException {
        while (size == items.length) {
            // 队列满了, 插入失败.
            // return;
            this.wait();
        }
        // 把新元素放到 tail 所在位置上
        items[tail] = elem;
        tail++;
        // 万一 tail 达到末尾, 就需要让 tail 从头再来.
        if (tail == items.length) {
            tail = 0;
        }
        // tail = tail % items.length;
        size++;
        this.notify();
    }

    // 出队列
    synchronized public Integer take() throws InterruptedException {
        while (size == 0) {
            // return null;
            this.wait();
        }
        int value = items[head];
        head++;
        if (head == items.length) {
            head = 0;
        }
        size--;
        this.notify();
        return value;
    }
}
public class Test {
    public static void main(String[] args) {
        MyBlockingQueue2 queue = new MyBlockingQueue2();
        // 消费者
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    int value = queue.take();
                    System.out.println("消费: " + value);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 生产者
        Thread t2 = new Thread(() -> {
            int value = 0;
            while (true) {
                try {
                    System.out.println("生产: " + value);
                    queue.put(value);
                    value++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        t2.start();

    }
}

Semaphore实现生产者消费者

这个示例代码使用了一个信号量(Semaphore)来控制生产者和消费者之间的同步。信号量是一个计数器,用于控制资源的数量。在这个示例中,信号量的初始值等于容器的容量,每次生产者生产一个新元素时,它会调用acquire()方法获取一个资源,并将信号量减一。每次消费者消费一个元素时,它会调用release()方法释放一个资源,并将信号量加一。这样,如果容器已满,生产者将等待直到有可用资源;如果容器为空,消费者将等待直到有新元素可用。

import java.util.LinkedList;  
import java.util.Queue;  
import java.util.concurrent.Semaphore;  
  
public class ProducerConsumer {  
    private static final int MAX_CAPACITY = 5;  
    private static final Semaphore semaphore = new Semaphore(MAX_CAPACITY);  
    private static final Queue<Integer> queue = new LinkedList<>();  
  
    public static void main(String[] args) {  
        Thread producerThread = new Thread(new Producer());  
        Thread consumerThread1 = new Thread(new Consumer());  
        Thread consumerThread2 = new Thread(new Consumer());  
        Thread consumerThread3 = new Thread(new Consumer());  
        Thread consumerThread4 = new Thread(new Consumer());  
  
        producerThread.start();  
        consumerThread1.start();  
        consumerThread2.start();  
        consumerThread3.start();  
        consumerThread4.start();  
    }  
  
    private static class Producer implements Runnable {  
        @Override  
        public void run() {  
            for (int i = 0; i < 10; i++) {  
                try {  
                    semaphore.acquire();  
                    queue.add(i);  
                    System.out.println("Produced: " + i);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  
  
    private static class Consumer implements Runnable {  
        @Override  
        public void run() {  
            while (true) {  
                try {  
                    if (!queue.isEmpty()) {  
                        int item = queue.poll();  
                        System.out.println("Consumed: " + item);  
                        semaphore.release();  
                    } else {  
                        Thread.sleep(1000); // Wait for new items if queue is empty  
                    }  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  
}

哲学家就餐

public static void main(String[] args) {
    Chopstick c1 = new Chopstick("1");
    Chopstick c2 = new Chopstick("2");
    Chopstick c3 = new Chopstick("3");
    Chopstick c4 = new Chopstick("4");
    Chopstick c5 = new Chopstick("5");
    new Philosopher("苏格拉底", c1, c2).start();
    new Philosopher("柏拉图", c2, c3).start();
    new Philosopher("亚里士多德", c3, c4).start();
    new Philosopher("赫拉克利特", c4, c5).start();    
    new Philosopher("阿基米德", c5, c1).start();
}
class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;
    public void run() {
        while (true) {
            // 尝试获得左手筷子
            if (left.tryLock()) {
                try {
                    // 尝试获得右手筷子
                    if (right.tryLock()) {
                        try {
                            System.out.println("eating...");
                            Thread.sleep(1000);
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }
}
class Chopstick extends ReentrantLock {
    String name;
    public Chopstick(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}
  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值