文章目录
-
- 1、写三种单例模式的实现方式
- [2、编号为 1-n 的循环报 1-3,报道 3 的出列,求最后一人的编号](#2、编号为 1-n 的循环报 1-3,报道 3 的出列,求最后一人的编号)
- [3、写两个线程打印 1-n,⼀个线程打印奇数,⼀个线程打印偶数](#3、写两个线程打印 1-n,⼀个线程打印奇数,⼀个线程打印偶数)
- [4、LRU 缓存实现](#4、LRU 缓存实现)
- [5、用Java 实现栈](#5、用Java 实现栈)
- 6、加权轮询算法的实现
- 7、手写死锁
- 8、快速排序
- 9、生产者消费者
1、写三种单例模式的实现方式
1、枚举
简单高效,无需加锁,线程安全,可以避免通过反射破坏枚举单例
java
public enum Singleton {
INSTANCE;
public void doSomething(String str) {
System.out.println(str);
}
}
2、静态内部类
- 当外部类 Singleton 被加载的时候,并不会创建静态内部类SingletonInner 的实例对象
- 只有当调用getInstance() 方法时, SingletonInner 才会被加载,此时才会创建单例对象 INSTANCE
- INSTANCE 的唯⼀性、创建过程的线程安全性,都由 JVM 来保证
- 无需加锁,线程安全,并且支持延时加载
java
public class Singleton {
// 私有化构造⽅法
private Singleton() {
}
// 对外提供获取实例的公共⽅法
public static Singleton getInstance() {
return SingletonInner.INSTANCE;
}
// 定义静态内部类
private static class SingletonInner{
private final static Singleton INSTANCE = new Singleton();
}
}
3、双重检验锁
java
public class Singleton {
private volatile static Singleton uniqueInstance;
// 私有化构造⽅法
private Singleton() {}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进⼊加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
uniqueInstance = new Singleton(); 三步执行:
- 为 uniqueInstance 分配内存空间
- 初始化uniqueInstance
- 将uniqueInstance指向分配的内存地址
2、编号为 1-n 的循环报 1-3,报道 3 的出列,求最后一人的编号
约瑟夫环问题
- 要求出最后留下的那个⼈的编号
- 求全过程,即要算出每轮出局的人
公式: (f(n - 1, k) + k - 1) % n + 1
f(n,k) 表示 n 个人报数,每次报数报 到 k 的人出局,最终最后一个人的编号
java
public class Josephus {
// 定义递归函数
public static int f(int n, int k) {
// 如果只有⼀个⼈,则返回 1
if (n == 1) {
return 1;
}
return (f(n - 1, k) + k - 1) % n + 1;
}
public static void main(String[] args) {
int n = 10;
int k = 3;
System.out.println("最后留下的那个⼈的编号是:" + f(n, k));
}
}
3、写两个线程打印 1-n,⼀个线程打印奇数,⼀个线程打印偶数
- 线程的等待/通知机制( wait() 和 notify() )
- 信号量Semaphore
java
package com.mys.leetcode;
/**
* @author mys
* @date 2023/10/26 20:32
*/
public class ParityPrinter {
private int max; // 总打印次数
private int number = 1; // 打印次数
private boolean odd; // 是否该打印奇数
public ParityPrinter(int max) {
this.max = max;
}
/**
* 打印奇数
*/
public synchronized void printOdd() {
while (number < 100) {
// 如果当前应该打印偶数,就等待
while (odd) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number ++;
odd = true;
notify(); // 唤醒另一个线程
}
}
/**
* 打印偶数
*/
public synchronized void printEven() {
while (number < 100) {
// 如果当前应该打印偶数,就等待
while (!odd) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number ++;
odd = false;
notify(); // 唤醒另一个线程
}
}
public static void main(String[] args) {
ParityPrinter printer = new ParityPrinter(100);
Thread t1 = new Thread(printer::printOdd);
Thread t2 = new Thread(printer::printEven);
t1.start();
t2.start();
}
}
Thread-0:1
Thread-1:2
Thread-0:3
Thread-1:4
Thread-0:5
Thread-1:6
Thread-0:7
Thread-1:8
4、LRU 缓存实现
LRU:最近最少使用
适用场景:频繁访问(缓存、页面置换)、有局部性、数据分布均匀、缓存容量适中
使用LinkedHashMap实现LRUCache
LinkedHashMap内部维护了双链表,增加了元素的顺序信息
removeEldestEntry:是否移除老数据
java
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private int capacity;
public LRUCache(int capacity) {
// LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
super(capacity, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当缓存元素个数超过容5时,移除最老的元素
return size() > capacity;
}
public static void main(String[] args) {
// 创建一个容量为3的LRU缓存
LRUCache<Integer, String> lruCache = new LRUCache<>(3);
// 添加数据
lruCache.put(1, "one");
lruCache.put(2, "two");
lruCache.put(3, "three");
// 此时缓存为:{1=One, 2=Two, 3=Three}
System.out.println(lruCache);
// 访问某个元素,使其成为最近访问元素
String value = lruCache.get(2);
// 此时缓存为:{1=One, 3=Three, 2=Two}
System.out.println(lruCache);
// 添加新数据,触发淘汰
lruCache.put(4, "Four");
// 此时缓存为:{3=Three, 2=Two, 4=Four}
System.out.println(lruCache);
}
}
{1=One, 2=Two, 3=Three}
{1=One, 3=Three, 2=Two}
{3=Three, 2=Two, 4=Four}
5、用Java 实现栈
java
public class Stack {
private int[] arr;
private int top;
public Stack(int capacity) {
arr = new int[capacity];
top = -1;
}
public void push(int element) {
if (top == arr.length - 1) {
throw new IllegalStateException("栈已满");
}
top ++;
arr[top] = element;
}
public int pop() {
if (isEmpty()) {
throw new IllegalStateException("栈为空");
}
int element = arr[top];
top --;
return element;
}
public int top() {
if (isEmpty()) {
throw new IllegalStateException("栈为空");
}
return arr[top];
}
public boolean isEmpty() {
return top == -1;
}
public int size() {
return top + 1;
}
}
6、加权轮询算法的实现
加权轮询: 它通过给不同的服务器分配不同的权重来实现请求的均衡分发
- 为服务器分配初始权重,权重越高,可以处理更高的请求
- 新请求到达,负载均衡器选择权重最高的机器处理请求
- 处理之后,服务器权重减去一个设定好的值,减小权重
- 所有服务器权重为0,重新赋初值
java
class Server {
private String name;
private int weight;
public Server(String name, int weight) {
this.name = name;
this.weight = weight;
}
public String getName() {
return name;
}
public int getWeight() {
return weight;
}
}
public class WeightedRoundRobin {
/*
1. 为服务器分配初始权重,权重越高,可以处理更高的请求
2. 新请求到达,负载均衡器选择权重最高的机器处理请求
3. 处理之后,服务器权重减去一个设定好的值,减小权重
4. 所有服务器权重为0,重新赋初值
*/
private List<Server> servers;
private int currentIndex;
public WeightedRoundRobin(List<Server> servers) {
this.servers = servers;
currentIndex = 0;
}
// 根据服务器权重,选择下一个服务器
public Server getNextServer() {
int totalWeight = calculateTotalWeight(); // 所有服务器总权重
int maxWeight = findMaxWeight(); // 所有服务器中的最大权重
int gcd = calculateGCD(); // 所有权重的最大公约数
// 根据最大权重遍历
while (true) {
currentIndex = (currentIndex + 1) % servers.size();
if (currentIndex == 0) {
maxWeight -= gcd;
if (maxWeight <= 0) {
maxWeight = findMaxWeight();
if (maxWeight == 0) {
return null; // 所有服务器的权重都为0,无法分配请求
}
}
}
if (servers.get(currentIndex).getWeight() >= maxWeight) {
return servers.get(currentIndex);
}
}
}
// 计算所有服务器总权重
private int calculateTotalWeight() {
int totalWeight = 0;
for (Server server : servers) {
totalWeight += server.getWeight();
}
return totalWeight;
}
// 找到所有服务器中的最大权重
private int findMaxWeight() {
int maxWeight = 0;
for (Server server : servers) {
if (server.getWeight() > maxWeight) {
maxWeight = server.getWeight();
}
}
return maxWeight;
}
// 计算所有权重的最大公约数
private int calculateGCD() {
int weightsLength = servers.size();
int[] weights = new int[weightsLength];
for (int i = 0; i < weightsLength; i ++) {
weights[i] = servers.get(i).getWeight();
}
return findGCD(weights, weightsLength);
}
// 找多个权值的最大公约数
private int findGCD(int[] weights, int n) {
int result = weights[0];
for (int i = 0; i < n; i ++) {
result = gcd(result, weights[i]);
}
return result;
}
// 计算两个数的最大公约数
private int gcd(int a, int b) {
if (b == 0) {
return a;
} else {
return gcd(b, a % b);
}
}
public static void main(String[] args) {
List<Server> servers = new ArrayList<>();
servers.add(new Server("Server1", 3));
servers.add(new Server("Server2", 5));
servers.add(new Server("Server3", 1));
WeightedRoundRobin wrr = new WeightedRoundRobin(servers);
// 模拟请求分发
for (int i = 0; i < 10; i++) {
Server server = wrr.getNextServer();
System.out.println("Request " + (i + 1) + " sent to: " + server.getName());
}
}
}
7、手写死锁
定义、四个必要条件、避免死锁、银行家算法、安全状态
java
public class DeadLockDemo {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + " get resource1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + " wait resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + " get resource2");
}
}
}, "线程1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + " get resource2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + " wait resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + " get resource1");
}
}
}, "线程2").start();
}
}
Thread[线程1,5,main] get resource1
Thread[线程2,5,main] get resource2
Thread[线程1,5,main] wait resource2
Thread[线程2,5,main] wait resource1
8、快速排序
- 确定分界点:q[l] q[(l+r)/2] q[r] 随机
- 调整范围:<x放左边,>x放右边
- 递归:处理左右两端
代码思想:两指针同时往中间走,若i指向的数<x,指针后移;若j指向的数>x,指针前移。完成之后指针指向的数swap,继续进行下一次循环
java
public void quickSort(int[] nums, int l, int r) {
if (l >= r) return;
int i = l - 1, j = r + 1, x = nums[(l + r) / 2];
while (i < j) {
do i ++; while (nums[i] < x);
do j --; while (nums[j] > x);
if (i < j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
quickSort(nums, l, j);
quickSort(nums, j + 1, r);
}
思想2:
- 从后往前走,找比x更小的数,放在low位置;
- 从前往后走,找比x更大的数,放在high位置
- 当low>=high,一轮循环结束,确定了x的位置,以x为中心,将数组分为左右两边,左右两边的数组继续循环
eg: 5 1 7 3 1 6 9 4 x=5
第一趟:4 1 1 3 5 6 9 7 确定了5;x1=4, x2=6
第二趟:3 1 1 4 5 6 9 7 确定了4,5,6;x1=3, x2=9
第三趟:1 1 3 4 5 6 7 9
9、生产者消费者
问题:如何确保⽣产者不会往满了的数据缓冲区继续添加数据,而消费者也不会从空的缓冲区取数据------引入⼀个共享的缓冲区
Procuder
java
public class Producer implements Runnable{
private final BlockingQueue<Integer> queue;
private final int MAX_SIZE = 5; // 缓冲区最大容量
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 1; i <= 10; i ++) {
produce(i); // 生产
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void produce(int item) throws InterruptedException {
synchronized (queue) {
while (queue.size() == MAX_SIZE) {
System.out.println("缓冲区已满,生产者等待");
queue.wait(); // 冲区已满,生产者等待
}
queue.put(item); // 生产者放入缓冲区
System.out.println("生产物品:" + item);
queue.notifyAll(); // 唤醒所有等待的消费者
}
}
}
Comsumer
java
public class Consumer implements Runnable{
private final BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
consume(); // 消费物品
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void consume() throws InterruptedException {
synchronized (queue) {
while (queue.isEmpty()) {
System.out.println("缓冲区空,消费者等待");
queue.wait(); // 缓冲区空,消费者等待
}
int item = queue.take(); // 从缓冲区取物品
System.out.println("消费者消费:" + item);
queue.notifyAll(); // 唤醒所有等待的生产者
}
}
}
Test
java
public class ProducerConsumerExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
Thread producerThread = new Thread(producer);
Thread consumerThread = new Thread(consumer);
producerThread.start();
consumerThread.start();
}
}
生产物品:1
生产物品:2
生产物品:3
生产物品:4
生产物品:5
缓冲区已满,生产者等待
消费者消费:1
消费者消费:2
消费者消费:3
消费者消费:4
消费者消费:5
缓冲区空,消费者等待