Java面试-每日十题

目录

1.共享锁与独占锁

共享锁

独占锁

2.锁的状态与锁升级

1.锁的状态分为四种

2.锁升级

3.重量级锁(Mutex Lock)

1.含义

2.缺点

3.Synchronized:本质上依赖于重量级锁实现

4.轻量级锁

5.偏向锁

1.含义

2.优点

6.锁优化操作

1.减少锁持有时间

2.减小锁粒度

3.锁分离

4.锁粗化

5.锁消除

7.线程池的组成

8.拒绝策略

1.使用场景

2.JDK内置策略

9.线程池的工作过程

1.线程池创建

2.调用execute()添加任务

3.完成任务

4.keepAliveTime管理线程

10.Java阻塞队列


1.共享锁与独占锁

共享锁

        共享锁允许多个线程同时获取锁并发访问共享资源

        共享锁是一种乐观锁,放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源

        ReadWriteLock就是一个典型的共享锁

独占锁

        独占锁只有一个线程能持有锁

        独占锁是一种悲观锁,限制了不必要的并发性

2.锁的状态与锁升级

1.锁的状态分为四种

        无锁状态、偏向锁、轻量级锁、重量级锁。

2.锁升级

        随着锁的竞争,锁进行单向升级,从低到高升级。锁可以从偏向锁升级到轻量级锁,再升级到重量级锁

3.重量级锁(Mutex Lock)

1.含义

       依赖于操作系统所实现的锁称为重量级锁

2.缺点

        依赖于底层的操作系统来实现锁,而操作系统实现线程之间的切换需要从用户态转换为内核态,需要花费比较长的时间,且性能消耗很高

3.Synchronized:本质上依赖于重量级锁实现

        synchronized是通过对象内部的监视器锁monitor来实现的,监视器锁本质上依赖于底层的操作系统的重量级锁来实现。因此synchronized效率低。jdk6为synchronized做了不少优化,核心都是减少重量级锁的使用

4.轻量级锁

1.含义

         用来在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。

2.作用

        轻量级锁能够在线程交替执行同步代码块时提高性能;但若出现同一时间访问同一把锁的情况,比如调用了锁对象的wait()和notify()方法,就会从轻量级锁升级为重量级锁

5.偏向锁

1.含义

        在多线程并发执行下,若锁没有存在多线程竞争,而是由同一线程多次获得锁,此时该锁就进入了偏向状态。

2.优点

        引入偏向锁后,在无多线程竞争的情况下减少了不必要的轻量级锁执行路径,从而在只有一个线程执行同步代码块时提高性能

6.锁优化操作

1.减少锁持有时间

        只在有线程安全要求的程序上加锁

2.减小锁粒度

        将一个可能被很多线程访问的对象拆分成一个个小部分,从而增加并行度,减少锁竞争;降低了锁的竞争后,偏向锁、轻量级锁的成功率才会高

3.锁分离

        将锁的功能进行分离,只要对操作不影响就可以分离。比如读写锁ReadWriteLock,根据功能将锁分离成读锁和写锁,保证了线程安全,提高了性能

4.锁粗化

        为保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,操作完共享数据后立即释放锁。但若对同一个锁不停的请求、同步、释放,本身也会消耗很多资源,因此需要粗化处理;将所有对锁的操作整合成锁的一次请求,从而减少对锁的请求同步次数,这就是锁的粗化。

5.锁消除

        在编译时,如果发现不可能被共享的数据,则可以消除对这个数据的锁操作。

7.线程池的组成

1.线程池管理器:用于创建并管理线程

2.工作线程:线程池中的线程

3.任务接口:每个任务必须实现的接口,用于工作线程调度任务运行

4.任务队列:用于存放待处理的任务,提供一种缓冲机制

以下以代码的形式具体了解线程池的组成:

代码示例:线程池实现需要使用的ThreadPoolExecutor类

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

按照注释可知:

corePoolSize:线程池中的线程数
maximumPoolSize:线程池中的最大线程数
keepAliveTime:当前线程池数量超过 corePoolSize 时,多余空闲线程在终止前等待新任务的最长时间。即多长时间内会被销毁
unit: keepAliveTime 的时间单位
workQueue:用于容纳任务的队列,被提交但尚未被执行的任务
threadFactory:线程工厂,用于创建线程
defaultHandler:默认拒绝策略,当任务太多来不及处理,如何拒绝任务

8.拒绝策略

1.使用场景

        当线程池中的线程被用完,且等待队列已满,此时需要使用拒绝策略处理新任务

2.JDK内置策略

1.AbortPolicy-中止策略:直接抛出异常

2.CallerRunsPolicy-调用方运行策略:在调用者线程中运行当前被丢弃的任务

3.DiscardOldestPolicy-丢弃最旧策略:丢弃最老的请求,尝试再次提交当前任务

4.DiscardPolicy-丢弃策略:丢弃无法处理的任务

9.线程池的工作过程

1.线程池创建

        线程池创建好后,还没有一个线程,等待任务队列传入。

2.调用execute()添加任务

        添加任务时,需要做判断:

                1)若正在运行的线程数小于corePoolSize,则创建线程运行该任务

                2)若正在运行的线程数大于等于corePoolSize,则将该任务放入队列

                        a)若队列已满,正在运行的线程数量小于maximumPoolSize,则创建线程运行任务

                        b)若队列已满,正在运行的线程数量大于等于maximumPoolSize,则抛出异常

3.完成任务

        当线程完成任务后,从任务队列中执行下一个任务

4.keepAliveTime管理线程

        若一个线程超过keepAliveTime没有任务,则线程池判断若当前运行线程数大于corePoolSize,就终止该线程。

10.Java阻塞队列

线程阻塞具有两种情况:

1.当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞,直到有数据放入队列

2.当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞,直到队列中有空位置,线程被自动唤醒

你可能感兴趣的:(Java面试-每日十题,java,面试,开发语言)