进程、线程、锁面试前复习(尽力局)
进程、线程、锁:
一. 进程和线程的区别是什么?
1.进程是"资源分配"的最小单位,每个进程都享有一块内存空间,线程是"系统调度"的最小单位。
2.线程是轻量级的进程,进程结束所有线程一定都被结束,但是线程结束进程不一定刚结束。
3.一个进程中至少有一个线程。
4.控制进程的模块是PCB进程控制模块,里面包含PID(进程标识符)、进程状态、进程上下文、指令计数器等。
控制线程的模块是TCB线程控制模块,同样也有,线程上下文、线程标识符、寄存器等。
二.造成线程安全问题的主要因素有哪些?
1.线程资源调度是随机的,抢占式的。
2.多个线程同时修改同一变量
3.修改操作不是原子的
4.内存可见性问题
5.指令重排序问题
三.对于操作并非原子性的怎么解决?
1.可以对所操作的对象进行加锁(synchronized),可以视为将多个操作打包成一个指令。
四.造成死锁的原因是什么?
1.锁的互斥性
2.锁的不抢占性
3.请求和保持
4.循环等待
五.如何减少死锁的情况?
1.如果当线程一拿着锁A时再去访问锁B,就会造成请求与保持,可以让线程一先释放锁A再去拿锁B资源。(针对请求与并保持设计)
2.如果线程一拿着锁A的资源去访问拿着锁B的资源的线程二,线程二拿着锁B的资源去访问拿着锁A资源的线程一,此时就会造成循环等待。此时我们需要规定获取资源的顺序,可以规定先获取资源A在获取资源B。
六.内存可见性安全问题时如何引起的?该如何解决?
1.内存可见性问题属于JVM在编译时对代码的优化,优化出的BUG。当我们一个变量经过多次判断后发现没有变化,此时接下来用到该变量时,该变量的值直接从缓存器中读取,不再向内存中重新读取,此时如果中途修改的变量的内容,该线程是感知不到的!!
解决方案:可以在该变量前面加volatile关键字,意思是每次编译该变量时需要重新从内存中读取值!!
七.说明一下线程池的几个参数?并说明当线程不够用时,4个处理策略
线程池中一共有7个参数:
1.coreThread:核心线程数(创建线程池时就创建好的线程数量)
2.maxThread:最大线程数
3.AliveTime::处核心线程数外其他线程被创建后如果没有被使用,销毁的时间
4.TimeUnit:销毁时间的单位
5.BlockingQueue:工作队列,一般是消息队列,符合生产者消费者模型
6.ThreadFactory:线程池,创建线程的工厂
7.RejectExcutionHandler:任务的拒绝策略
拒接策略一共有以下四个:
1.丢弃当前的任务,直接抛出异常
2.交给执行submit方法的线程处理该任务
3.将之前旧的任务丢弃,执行新任务
4.直接将新任务丢弃,执行原来的任务
八.写一个单例模式\阻塞队列\线程池\定时器
//单例模式(懒汉模式)
public class SingleInstance {
private static volatile SingleInstance singleInstatnce;
private SingleInstance() {
}
public static SingleInstance getInstance() {
if(singleInstatnce == null) {
synchronized (SingleInstatnce.class) {
if (singleInstatnce == null) {
singleInstatnce = new SingleInstance();
}
}
}
return singleInstatnce;
}
}
//阻塞队列
class MyBlockingQueue {
private static List<Object> objectList;
private static int head;
private static int tail;
private static int size;
public MyBlockingQueue(int count) {
objectList = new ArrayList<>(count);
}
//向阻塞队列中放元素
public void put(Object o) throws InterruptedException {
synchronized (objectList) {
if(size == 0) {
//此时应该阻塞等待
objectList.wait();
}else {
++tail;
if(tail >= objectList.size() ) {
tail = 0;
}
++size;
objectList.add(o);
//唤醒get方法
objectList.notify();
}
}
}
public Object get() throws InterruptedException {
Object result = null;
synchronized (objectList) {
if(size >= objectList.size()) {
//队列满了阻塞的等待
objectList.wait();
}else {
++head;
if(head >= objectList.size()) {
head = 0;
}
result = objectList.remove(head);
++size;
//唤醒put方法
objectList.notify();
}
}
return result;
}
}
//创建工作线程
class Work1 extends Thread{
//创建阻塞队列
BlockingQueue<Runnable> runnables = new LinkedBlockingQueue<>();
public Work1(BlockingQueue<Runnable> runnables) {
this.runnables = runnables;
}
//从阻塞队列获取任务并执行
//自动执行任务
@Override
public void run() {
while(true) {
try {
Runnable runnable = runnables.take();
//拿出任务
runnable.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class MyThreadPool {
//最大线程数
private int maxThreadCount;
private BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>();
public MyThreadPool(int maxThreadCount) {
this.maxThreadCount = maxThreadCount;
}
//存放线程的列表
private List<Thread> threadList;
public void submit(Runnable runnable) throws InterruptedException {
if(threadList.size() < maxThreadCount) {
Work1 work1 = new Work1(blockingQueue);
work1.start();
threadList.add(work1);
}
blockingQueue.put(runnable);
}
}
//定时器
class MyTime {
//优先级队列
private PriorityQueue<Worker> priorityQueue = new PriorityQueue<>();
private long curTime;
public MyTime() throws InterruptedException {
synchronized (this) {
if (priorityQueue.isEmpty()) {
//为空阻塞等待
this.wait();
}else {
Worker work = priorityQueue.peek();
if(curTime >= work.getTime()) {
//时间到了或者超时了需执行
work.getRunnable().run();
priorityQueue.poll();
}else {
this.wait(work.getTime() - curTime);
}
}
}
}
//加入队列的方式
public void schedule(Runnable runnable,long delay) {
synchronized (this) {
Worker worker = new Worker(runnable, delay);
priorityQueue.offer(worker);
this.notify();
}
}
}
//存放信息与时间
class Worker implements Comparable<Worker>{
private long time;
private Runnable runnable;
public Worker(Runnable runnable,long delay) {
this.time = System.currentTimeMillis()+delay;
this.runnable = runnable;
}
//获取时间
public long getTime() {
return time;
}
//获取任务
public Runnable getRunnable() {
return runnable;
}
@Override
public int compareTo(Worker o) {
return (int)(this.time - o.time);
}
}
九.介绍一下synchronized锁,并说明与ReentrantLock锁的区别
synchronized锁:
1.synchronized锁是自适应锁,当锁冲突较小时,是一把乐观锁、轻量级锁、自旋锁、可重入锁、非公平锁。当所冲突变大时,该锁会变成悲观锁、重量级锁、可重入锁、挂起等待锁、非公平锁。
2.synchronized锁在进入同步块时自动加锁、出同步块时由JVM自动解锁
与ReentrantLock锁区别:
1.ReentrantLock时java库中的类,但是synchronized是一个关键字
2.ReentrantLock加锁必须用lock()方法,解锁必须用unlock()方法,synchrnized自动加解锁。
3.ReentrantLock本身是一把非公平锁,但是提供了公平锁的方法,使其成为一把公平锁。
4.ReentractLock可以实现多路选择通知,但是synchronized中使用wait/notify、notifyAll,只能唤醒一个线程或者全部线程。
十.什么是CAS,使用CAS有什么好处,会引发什么问题,怎么解决?
CAS是什么,又是什么好处?
1.CAS原意是CompareAndSwap,它的操作是原子性的。
2.需要判断内存中的值是否与寄存器1中的值是否相等,如果相等将内存中的值与缓存2中的值进行交换,该操作是原子性的。
3.CAS不需要加锁操作,也能够使线程安全,高效、便捷。
CAS会引发的问题:
CAS会引发ABA问题,也就是如果在进行内存总的值与寄存器1中的值进行判断的时候,如果相等,但是不意味着这个寄存器中的值就没有被修改过,有可能是+100之后又-100.尤其是在支付时容易引发错误。
解决方案:可以在执行操作时前,用版本号进行一个判断,每次操作成功版本号就+1,如果,后期交易结束后与预期的版本号不符,那么就证明在这之前把已经被修改过了。
十一.介绍一下ConcurrentHashMap与HashMap有什么区别
1.ConcurrentHashMap是线程安全的,HashMap是线程不安全的。
2.HashMap在扩容时,是一步到位,直接将所有内容迁移到新HashMap,容易造成,卡顿等情况。ConcurrentHashMap在扩容时时一步一步迁移,每调用一次ConcurrentHashMap就迁移一部分key。
3.HashMap在查找时直接在同一个表中查找,到那时ConcurrentHahsMap不一定,如果扩容之后还有没有完全迁移结束,就需要同时在两张表中一起查找。
十二.介绍一下ConcurrentHashMap与HashTable的区别
1.ConcurrentHashMap与HashTable虽然都是相对的线程安全,但是HashTable时将所有方法上面都直接加锁,操作起来效率较低,ConcurrentHashMap进行了粒度优化,在每个链表上面各加一把锁,如果同时修改同一个链表上的数据时才会出现锁冲突!
2.ConcurrentHashMap底层用道德数据结构是,数组+链表+红黑树。HashTable用到是数组+链表。
3.当出现hash冲突时,HashTable是通过链表来解决,但是ConcurentHashMap当链表长度超多一定阈值时,转化为红黑树。(改善查询效率)
十三.谈一谈Runnable与Callable的区别
1.Runnable是一个功能性接口,该接口无返回值。Callable是一个泛型接口,该接口有返回值。
2.Runnable接口中的run()方法不能抛出异常,Callable接口中的call()方法可以抛出异常,外部可以进行捕获!!
十四.什么是读写锁,什么是偏向锁
1.读写锁是允许多个线程同时可以进行读操作共享资源,但是写操作是互斥的。synchronized并不是读写锁。
2.偏向锁是当对一个对象或操作第一加锁前进行的一个标记。
十五.谈一谈锁升级、锁消除、锁粗化
1.锁升级:无锁-->偏向锁-->轻量级锁-->重量级锁.
2.锁消除:编译器会对写有synchronized的代码进行判断,如果该地方没有必加锁,编译器在编译阶段会自动将该锁优化掉。
3.错粗化:编译器将多个细粒度的锁合成为一个粗粒度的锁。(粒度可以理解为代码的多少)