进程、线程、锁面试前复习(尽力局)

发布于:2025-03-10 ⋅ 阅读:(16) ⋅ 点赞:(0)

进程、线程、锁:

      一.  进程和线程的区别是什么?

        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.错粗化:编译器将多个细粒度的锁合成为一个粗粒度的锁。(粒度可以理解为代码的多少)