一、Java
1.1 58同城java字符串常量池《深入理解JAVA虚拟机》
运行结果:
58tongcheng 58tongcheng true java java false
面试解析:
系统初始化JDK自带的java,不是同一个java。
是加载sun.misc.Version这个类的时候进入常量池中的。
1.2 字节跳动两数求
1.给定一个数m,求大于该数的最小2的n次密,返回n
2.给定一个数组nums和一个目标值target,请你在数组中找出和为目标值的哪两个整数,并返回数组下标
数组暴力破解:
class Solution { public int[] twoSum(int[] nums, int target) { int[] result = new int[2]; //定义返回结果数组 for (int i = 0; i < nums.length; i++) { for (int j = 0; j < nums.length; j++) { if (nums[j] == target - nums[i]) { //用target-nums[i]作为查询条件来判断结果 if (i != j) { result[0] = j; result[1] = i; break; } } } } return result; //如果没有返回的是空数组 } }
HashMap数据结构解法:
Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; if (map.containsKey(complement)) {//满足条件直接返回结果,不继续遍历存数据 return new int[] { map.get(complement), i }; } map.put(nums[i], i); } throw new IllegalArgumentException("No two sum solution"); }
二、JUC
2.1 可重入锁【递归锁】
在同一个线程在外层方法获取的锁的时候,在即进入该线程的内层方法会自动获取锁,不会阻塞。
ReentrantLock synchronzied 都是可重入锁【同步锁】。
Synchronzied: 隐式锁 JVM控制 【自动挡】
ReentrantLock: 显示锁Lock ,手动来写 【手动挡】
也是说,同一个线程可以多次获得同一把锁。
ReentrantLock 也是一样的。
2.2 唤醒线程的3种方法
1.Object中的wait() notify()
notify()放于wait 之前无法执行,无法唤醒,程序一直等待下去。
必须成对,先wait再notify
2.JUC包中的Condition的 await() signal()
限制条件和上面如法炮制。
3.LockSupport类park() unpark() 可以阻塞当前线程,以及指定被阻塞的线程
单纯的唤醒和阻塞线程,不需要再锁里面。
2.3 LockSupport【wait/notify的改良版park/unpark】
用于创建锁和其他基本线程阻塞原语。
每一个线程都有一个许可证,permit 不是1就是0,可以看成信号量Semaphore,与其不同的是累加的上限为1.
先等待后唤醒 先唤醒后等待 都是可以的。park()形同虚设。
三、AQS【2021年重点】
抽象的队列同步器 ,构建锁,其他同步器组件及整个JUC体系的基石框架。
FIFO队列来完成排对工作,并通过一个int类型表示锁的持有状态。
ReentrantLock
CountDownLantch
ReentrantReadWriteLock
Semaphore
与区别于CAS比较并交换,自旋锁。
3.1 锁与同步器的关系?
锁:面向使用者,定义了程序员与锁交互的应统API,隐藏了实现的细节。
同步器:面向所的实现者,java并发大神DougLee,提出了一种规范并简化了锁的实现,屏蔽了同步状态的管理,阻塞线程排对和通知,唤醒机制等。
AQS = State变量+CLH变种的双端队列
头尾前后指针全部都有
非公平锁为例,银行办理业务的案例,模拟AQS如何进行现成的管理和通知唤醒机制:
public class Demo01 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); /** * 3 个线程模拟银行网点,受理顾客 */ // 第一个顾客办理业务,没有人等待,A直接办理20分钟 new Thread(()->{ lock.lock(); try{ System.out.println("-----A线程进入-----"); // 暂停20分钟 TimeUnit.MINUTES.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } },"A").start(); // 第二个顾客办理业务,受理的窗口只有一个,B只能等待 new Thread(()->{ lock.lock(); try{ System.out.println("-----B线程进入-----"); } finally { lock.unlock(); } },"B").start(); // 第三个顾客办理业务,受理的窗口只有一个,C只能等待 new Thread(()->{ lock.lock(); try{ System.out.println("-----C线程进入-----"); } finally { lock.unlock(); } },"C").start(); } }
1.A线程首先上锁,需要执行的:
2.BC线程抢不到,修改不了Status的状态,抢占失败,准备进行排队,执行acquire()方法,模板方法设计模式,抛出异常,
3.B尝试抢占锁,失败,继续尝试addWaiter()这个方法,准备入队候客区进行补位,
4.新建一个新的节点,并不是当前的节点B,而是一个null,作用就是哨兵节点,进行占位。
此时,就是成了如下的情况:
5.C线程开始准备加入队列,重复B线程的流程,入队执行addWaiter()方法,抢不到,C与B不同的是,有一个哨兵节点和B节点,
6.B开始进行阻塞,此时才真正的被阻塞,完全入队。
7.A线程办完业务,开始走人,执行unLock()方法
h为头指针,-1不为0,开始进行出队操作。unpark()。
开始进行状态变换,1-1=0,设置当前线程占有为null。释放锁,设置状态Status=0。
总结:
1.lock方法
2.AQS====调用tryAcquire()---->调用addWaiter()---->调用acquireQueued()方法
四、Spring
…
五、Redis
…