Java手写系列
- 前言
- 生产者消费者问题
- 交替打印A、B
- 死锁问题
- 快速排序
- 堆排序
- 归并排序
- LRU算法
- 单例模式
前言
这篇文章主要是以JAVA
代码展示一些基础及面试要求手写的代码段,想到啥就写啥,也将持续更新。
生产者消费者问题
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。 ------百度百科
生产者消费者有多种解法,下面就展示Synchronized的解法。
public class Test {
static class Stock{
//最大容量
private final int MAX_SIZE = 30;
private int cur_SIZE;
Object object = new Object();
public void produce() throws InterruptedException {
while(true){
synchronized (object){
while(cur_SIZE >= MAX_SIZE){
System.out.println(Thread.currentThread().getName() + "发现仓库已满");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
++cur_SIZE;
System.out.println(Thread.currentThread().getName() + "生产了产品" + cur_SIZE);
object.notifyAll();
}
Thread.sleep(100);
}
}
public void consume() throws InterruptedException {
while(true){
synchronized (object){
while(cur_SIZE <= 0){
System.out.println(Thread.currentThread().getName() + "发现仓库为空");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
--cur_SIZE;
System.out.println(Thread.currentThread().getName() + "消费了产品" + cur_SIZE);
object.notifyAll();
}
Thread.sleep(200);
}
}
}
public static void main(String[] args) {
Stock stock = new Stock();
new Thread(() -> {
try {
stock.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"生产者1001").start();
new Thread(() -> {
try {
stock.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"生产者1002").start();
new Thread(() -> {
try {
stock.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"消费者2001").start();
new Thread(() -> {
try {
stock.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"消费者2002").start();
}
}
生产者1001生产了产品1
生产者1002生产了产品2
消费者2001消费了产品1
消费者2002消费了产品0
生产者1002生产了产品1
生产者1001生产了产品2
....
生产者1002生产了产品30
生产者1001发现仓库已满
消费者2002消费了产品29
生产者1001生产了产品30
交替打印A、B
使用了ReentrantLock
的newCondition()方法
public class Test {
ReentrantLock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
private void printA(){
while(true){
lock.lock();
System.out.println("--A--");
try {
condition2.signal();
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void printB(){
while(true){
lock.lock();
System.out.println("--B--");
try {
condition1.signal();
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test test = new Test();
new Thread(() -> {
test.printA();
}).start();
new Thread(() -> {
test.printB();
}).start();
}
}
运行结果
--A--
--B--
--A--
--B--
--A--
--B--
--A--
--B--
--A--
--B--
--A--
死锁问题
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。-------百度百科
public class Test {
Object lockA = new Object();
Object lockB = new Object();
public void lockA2lockB(){
synchronized (lockA){
System.out.println("拿到A锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println("能进来这里吗?");
}
}
}
public void lockB2lockA(){
synchronized (lockB){
System.out.println("拿到B锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA){
System.out.println("能进来这里吗?");
}
}
}
public static void main(String[] args) {
Test test = new Test();
new Thread(() -> {
test.lockA2lockB();
},"线程A").start();
new Thread(() -> {
test.lockB2lockA();
},"线程B").start();
}
}
拿到A锁
拿到B锁
(可以看到没有输出“能进来这里吗?”)
快速排序
算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
- 从数列中挑出一个元素,称为 “基准”(target);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
public void quickSort(int[] arr,int left,int right){
if(left >= right || left < 0 || right >= arr.length){
return;
}
int index = partition(arr,left,right);
if(index == left){
quickSort(arr,index + 1,right);
}else if(index == right){
quickSort(arr,left,right - 1);
}else{
quickSort(arr,left,index - 1);
quickSort(arr,index + 1,right);
}
}
public int partition(int[] arr,int left,int right){
int target = arr[left];
int index = left;
for(int i = left + 1;i <= right;i++){
if(arr[i] < target){
index++;
swap(arr,index,i);
}
}
swap(arr,index,left);
return index;
}
public static void swap(int[] arr,int i ,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] nums = new int[]{2,5,73,13,34,90,24,64};
Test test = new Test();
test.quickSort(nums,0,nums.length - 1);
for (int num : nums){
System.out.println(num);
}
}
运行截图
2
5
13
24
34
64
73
90
算法分析
最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(nlogn)
堆排序
算法描述
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
public class HeapSort {
public static void sort(int[] arr) {
int n = arr.length;
// 创建最大堆
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 从最后一个节点开始依次取出并调整堆
for (int i = n - 1; i > 0; i--) {
// 将最大值移动到数组末尾
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 调整堆
heapify(arr, i, 0);
}
}
// 调整以i为根节点的堆,使其满足最大堆的性质
private static void heapify(int[] arr, int n, int i) {
int largest = i; // 假设当前节点值最大
int left = 2 * i + 1; // 左子节点索引
int right = 2 * i + 2; // 右子节点索引
// 如果左子节点值比当前节点值大,更新最大值索引
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
// 如果右子节点值比当前节点值大,更新最大值索引
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// 如果最大值索引变化,调整堆
if (largest != i) {
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
heapify(arr, n, largest);
}
}
public static void main(String[] args) {
int[] arr = { 64, 34, 25, 12, 22, 11, 90 };
HeapSort.sort(arr);
System.out.println(Arrays.toString(arr)); // [11, 12, 22, 25, 34, 64, 90]
}
}
算法分析
最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)
归并排序
算法描述
把长度为n的输入序列分成两个长度为n/2的子序列;
对这两个子序列分别采用归并排序;
将两个排序好的子序列合并成一个最终的排序序列。
public static int[] MergeSort(int[] array) {
if (array.length < 2) return array;
int mid = array.length / 2;
int[] left = Arrays.copyOfRange(array, 0, mid);
int[] right = Arrays.copyOfRange(array, mid, array.length);
return merge(MergeSort(left), MergeSort(right));
}
public static int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
for (int index = 0, i = 0, j = 0; index < result.length; index++) {
if (i >= left.length)
result[index] = right[j++];
else if (j >= right.length)
result[index] = left[i++];
else if (left[i] > right[j])
result[index] = right[j++];
else
result[index] = left[i++];
}
return result;
}
public static void main(String[] args){
int[] arr = new int[]{1,4,3,7,2,9};
int[] sortArr = MergeSort(arr);
for (int i : sortArr) {
System.out.println(i + " ");
}
}
最佳情况:T(n) = O(n) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)
LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。------百度百科
作为面试必备的一种算法,必须要会!
public class LRU {
class LRUNode{
int key;
int value;
LRUNode next;
LRUNode pre;
public LRUNode(){
}
public LRUNode(int key,int value){
this.key = key;
this.value = value;
}
}
LRUNode head;
LRUNode tail;
int size;
int capacity;
HashMap<Integer,LRUNode> map = new HashMap<>();
public LRU(int capacity){
head = new LRUNode();
tail = new LRUNode();
head.next = tail;
tail.pre = head;
size = 0;
this.capacity = capacity;
}
public void put(int key,int value){
LRUNode node = map.get(key);
if(node == null){
LRUNode newNode = new LRUNode(key, value);
map.put(key,newNode);
addToHead(newNode);
size++;
if (size > capacity){
LRUNode removedNode = removeTail();
map.remove(removedNode.key);
size--;
}
}else {
node.value = value;
moveToHead(node);
}
}
public int get(int key){
LRUNode node = map.get(key);
if(node == null){
return -1;
}
moveToHead(node);
return node.value;
}
private void removeNode(LRUNode node){
node.pre.next = node.next;
node.next.pre = node.pre;
}
private LRUNode removeTail(){
LRUNode node = tail.pre;
removeNode(node);
return node;
}
private void addToHead(LRUNode node){
node.next = head.next;
head.next.pre = node;
head.next = node;
node.pre = head;
}
private void moveToHead(LRUNode node){
removeNode(node);
addToHead(node);
}
}
class Test{
public static void main(String[] args) {
LRU lru = new LRU(3); //容量为3
lru.put(1,10);
lru.put(4,40);
lru.put(7,70);
lru.get(4); //get了之后把4放到7前面,所以满3个之后是7没了,4还在
lru.put(2,20);
lru.put(6,60);
System.out.println("7还在吗?" + lru.get(7));
System.out.println(lru.map.keySet());
}
}
运行截图
7还在吗?-1
[2, 4, 6]
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
class Test1 {
private static TestClass instance;
private Test1(){
}
public static TestClass getInstance(){
if(instance == null){
synchronized (TestClass.class){
if(instance == null){ //因为可能有多个线程在等synchronized的释放,所以要再判断一次
instance = new TestClass();
}
}
}
return instance;
}
}
class Test2{
private static Test2 instance = new Test2();
private Test2(){
}
public static Test2 getInstance(){
return instance;
}
}
public class Test{
public static void main(String[] args) {
//懒汉式
TestClass instance1 = Test1.getInstance();
TestClass instance2 = Test1.getInstance();
System.out.println(instance1 == instance2);
//饿汉式
Test2 instance3 = Test2.getInstance();
Test2 instance4 = Test2.getInstance();
System.out.println(instance3 == instance4);
}
}