说一说自己对于 synchronized 关键字的了解
synchronized关键字解决多个线程之间访问资源的同步性,被synchronized关键字能保证被他修饰的方法和代码块在任意时刻都只有一个线程访问
说说自己是怎么使用 synchronized 关键字,在项目中用到了吗
synchronized关键字主要用于三种场景:①修饰实例方法:也就是给当前实例对象加锁,当进入到同步代码块之前需要获得当前对象的锁②修饰静态方法:也就是给当前类加锁,作用于改类的所有实例对象,而静态资源是不属于实例对象的,是属于类的。所以如果有一个线程访问synchronized修饰的非静态方法,另一个线程访问synchronize修饰的静态方法,是不会出现互斥的。因为synchronized修饰的非静态方法是当前实例对象的锁,而synchronize修饰的静态方法是类的锁。③修饰代码块:也就是给给定对象加锁,在访问同步代码块之前需要获得给定对象的锁
什么是双重检查锁实现单例?代码是什么样子的?
双重检查锁实现单例就是在创建单例对象的时候会进行两次判断实例是否存在,一次加锁判断一次不加锁判断
代码:
说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗
JDK1.6版本之后,对锁的做了很多优化,比如说:偏向锁,自旋锁,轻量级锁,锁粗化等技术来减少锁操作的开销。锁的状态有四种,分别是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,他们会随着竞争的激烈而提升,锁是可以提升但不可以降级,这种策略是为了提高获得锁和释放锁的效率。
谈谈 synchronized和ReentrantLock 的区别
相同:他们都是可重入锁
区别:①synchronized在竞争锁的时候会一直等待,ReentrantLock会尝试去获取锁,并将获取结果返回
②synchronized获取锁的时候不能设置超时时间,ReentrantLock可以设置超时时间
③synchronized不能满足公平锁,而ReentrantLock可以满足公平锁
④synchronized是依赖于JVM的,而ReentrantLock是依赖于JDK的API
⑤synchronized在执行锁代码块的时候出现异常会自动释放锁,而ReentrantLock不会,他需要在finally{}中释放锁
并发编程的三大特征以及volatile关键字的作用?
并发编程有三大特性,分别是:①原子性:一个操作或多次操作在执行过程中要么全部执行完成,要么全部不执行②可见性:当一个变量对共享变量进行修改值时,其他线程也可以同时第一时间获得变量修改之后的值③有序性:代码执行过程中是有序的
volatile关键字可以保证可变变量的可见性以及防止指令重排序
说说 synchronized 关键字和 volatile 关键字的区别
synchronized关键字和volatile关键字的作用是互补的
①volatile关键字是线程同步的轻量级实现,所以他的性能比synchronized的性能要高,但是volatile关键字只能修饰变量,而synchronized关键字可以修饰方法和代码块,所以我们使用synchronized关键字的场景比较多。②多线程访问volatile关键字不会发生线程堵塞,但是访问synchronize关键字可能会发生堵塞③volatile关键字能保证数据的可见性,但不能保证数据的原子性,而synchronized关键字两者都能保证④volatile关键字主要解决于多线程之间的变量可见性,而synchronized关键字主要是解决多线程之间的访问资源的同步性
ThreadLocal有什么作用?有哪些使用场景?
ThreadLocal是线程本地存储。每个线程都创建一个ThreadLocalMap对象,然后每个线程都可以访问自己的ThreadLocalMap对象中的value。通过这种方法,可以避免资源在多线程之间的共享。
常用的使用场景是给每一个线程分配一个JDBC的连接对象,这样子就可以保证
什么是线程池?
线程池就是创建若干个线程存放到一个池里面,当有任务需要处理的时候,就到线程池中 的任务队列中,等任务执行完成之后,线程不会被销毁,而是回到线程池中等待下一个任务。
线程池的优点
线程池的特点是:线程可复用 可设置最大并发量 可管理线程
线程池的优点是:降低资源消耗 提高响应效率 提高线程的可管理性
实现Runnable接口和Callable接口的区别
①Runnable的run()方法只能抛出运行时异常,并且不能处理,而Callable的call()方法可以捕捉异常,并且返回异常信息②Runnable的run()方法没有返回值,Callable的call()方法有返回值,并且支持泛型
执行execute()方法和submit()方法的区别是什么呢?
execute()方法用于提交不需要返回值的任务,任务提交之后我们没法判断任务是否在线程池中成功完成submit()方法用于提交需要返回值的任务,线程池会返回一个Future对象,我们可以通过Future对象判断任务是否成功完成,并且可以跳过Futrue中的get()方法获取到返回值
如何创建线程池?
我们可以通过两种方式来创建线程池:
第一种是通过构造器来实现,也就是ThreadPoolExecutor
第二种是通过Executor的工具类Executors来实现,我们可以创建三种线程池
FixedThreadPool:创建返回指定线程数量的线程池
SingleThreadExecutor:创建只返回一个线程对象的线程池
CacheThreadPool:创建可以根据实际情况创建线程数量的线程池
线程池的原理分析(执行流程)
当一个任务提交的时候回到线程池中判断核心线程数数量是否已经达到最大,如果不是的话就会创建一个线程,如果是的话就到任务队列中看是否已满,如果没满的话就会到任务队列中排队,如果满了的话就判断线程池的最大线程数是否已达到最大,如果没有则创建线程,如果已经达到最大的话就会执行RejectedExecutionHandle方法拒绝此次任务提交
介绍一下Atomic 原子类
Atomic就是原子类,在这里的意思是一旦操作执行就不会被打断,就算多个线程同时在执行,操作一旦开始,不会受其他线程的影响。它具有原子性的特征
什么是JUC
JUC是java.util.concurrent包的简称,他是一个工具包,里面包含了很多原子类,这些原子类可以更好的提高线程的高并发,已经避免多线程执行过程中出现死锁的情况
JUC 包中的原子类是哪4类?
JUC包中包括四种原子性类,分别是:整形原子型,数组类型原子型,引用类型原子型,基于对象类型修改原子型
整形原子型的类有:AtomicIntegerAtomicLongAtomicBoolean数组类型原子型:AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray引用类型原子型:AtomicReferenceAtomicStamtedReferenceAtomicMarkableReference基于对象类型修改原子型:AtomicStmtedReferenceAtomicIntegerFieldUpdater AtomicLongFieldUpdater
讲讲 AtomicInteger 的使用
AtomicInteger中有很多方法,比如getAndSet() , getAndIncrement() , getAndAdd()等方法,而我们使用的时候就创建一个AtomicInteger对象,然后调用对应的方法,再将调用方法后得出的结果赋值给一个int类型变量
能不能给我简单介绍一下 AtomicInteger 类的原理
AtomicInteger类是Atomic原子类中的整形原子类中的一个类,他是通过CAS(compare and swap)和volatile以及native方法来保证原子性操作,从而减少synchronize关键字的开销,提高执行效率
AQS 原理概览
AQS的基本原理就是:当一个请求进入到共享资源的时候,会判断这个共享资源是否为空闲,如果是的话就将当前这个线程设置成有效工作线程,并且将共享资源给锁住;如果不是的话,就会进入线程阻塞等待以及唤醒的时候分配锁的一个机制,这个机制是由CLH的队列锁实现的,如果暂时没有分配到锁的线程就会进入到队列中
shutdown() VS shutdownNow()
shutdown():关闭线程池,不再接受新的任务,但是回将队列里的任务执行完毕shutdownNow():关闭线程池,停止当前正在执行的任务,并且将队列中的任务停止处理,并将正在排队的返回一个List
常用的线程池
FixedThreadPool
SecheduledThreadPoolExecutor
CachedThreadPool
SingleThreadExecutor
什么是悲观锁和乐观锁
乐观锁:他总是很乐观,觉得不会发生并发问题,在他读取数据的时候觉得不会有其他线程来修改数据,所以不加锁,但是在修改数据的时候他会去判断在此之前是否有其他线程对数据进行修改,通常是使用CAS和版本号机制来操作
悲观锁:他总是很悲观,他觉得在数据读取的时候会有其他线程来修改数据,所以他总是加锁,如果其他线程要获取该数据的话,会出现阻塞状态,然后等到锁的时候再读取数据