本文主要是介绍对象面试官系列之Java并发--面试官看了都说好,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1.线程的实现
A.实现Runnable接口;多个线程共同完成一个任务
B.实现Callable接口;
与Runnable相比,Callable可以有返回值还可以抛出异常,返回值通过FutureTask进行封装。
FutureTask<String> futureTask = new FutureTask<String>(new CallTest());
new Thread(futureTask).start();
C.继承Thread类。(多个线程分别完成自己的任务)
D.线程池
2.线程状态
进程、线程:新建、就绪、运行、阻塞、终止
3.上下文切换
概念:CPU是通过给每个线程分配时间片并轮转的方式来实现多线程,当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换
减少上下文切换时间:1.无锁并发编程。多线程竞争锁时会引起上下文切换
2.CAS算法
3.使用最少线程
4.线程死锁
多个线程互相持有对方需要的资源从而被同时阻塞
必要条件:
互斥条件:该资源任意一个时刻只由一个线程占用。
请求与保持条件:一个进程因请求资源而阻塞时,不会释放已获得的资源。
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免:
破坏互斥条件:无法破坏,因为我们用锁本来就是想让他们互斥的。
破坏请求与保持条件:一次性申请所有的资源。
破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件
如何解除:
1.利用抢占:挂起某些进程,并抢占它的资源,将他分配给其他进程。但应防止某些进程被长时间挂起而处于饥饿状态;
2.利用回滚:让某些进程回退到足以解除死锁的地步,进程回退时自愿释放资源。要求系统保持进程的历史信息,设置还原点;
3.利用杀死进程:强制杀死某些进程直到死锁解除为止,可以按照优先级进行.
如何排查:jstack
5.sleep()和wait()区别
1.sleep()不会释放锁,Thread的静态方法;wait()会释放锁,是object下的方法
2.sleep会自动苏醒;wait()需要别的线程调用同一个对象上的notify()或者notifyAll()方法或者可以使用wait(long timeout)超时后线程会自动苏醒
3.sleep和wait都不会占用cpu资源
6.synchronized关键字
6.1 作用
synchronized关键字保证修饰的代码块和方法同一时间只有一个线程在执行;修饰静态方法和修饰代码块都是对类的class对象加锁(一个类多个对象会阻塞),修饰实例方法是给this对象实例加锁(一个类多个对象不会阻塞);
6.2 底层原理
修饰代码块:使用的是monitorenter和monitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置。当执行monitorenter指令时,线程会获取对象的监视器(monitor)。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit指令后,将锁计数器设为0,表明锁被释放。
修饰方法:synchronized会设置方法的一个标识符,然后获取对象的监视器(monitor)。
6.3 Jdk1.6优化
偏向级锁:
当锁对象第一次被线程获取的时候,使用CAS操作把获取到这个锁的线程的ID记录在对象的对象头之中,出现线程竞争时,CAS会失败,然后升级成轻量级锁
轻量级锁:持有偏向锁的对象会将对象头拷贝一份放在栈帧的锁记录中,然后使用CAS将对象头的轻量级锁指针指向锁记录,成功升级成轻量锁,失败会使用自旋获得锁,自旋到一定次数或者有第三个线程竞争升级成重量级锁
重量级锁:会把除了锁以外的线程都阻塞
锁消除:JVM在编译时会对synchronized中的代码进行扫描,判断是否会逃出去被其它线程访问,会进行锁消除从而减少线程请求锁空间。Stringbuffer
6.4 原子性、可见性、有序性
原子性:lock unlock是原子性的
可见性:加锁时会获取主内存的值到工作内存中,释放锁时会将工作内存的变量同步到主内存中
有序性:锁在同一时刻只能有一个线程获取
6.5 和lock的区别
1.lock需要主动unlock解锁,容易造成死锁;synchronized是自动释放锁
2.lock底层使用AQS实现的,可实现公平锁非公平锁;synchronized是通过字节码控制对象的监视器锁实现,只有非公平锁实现。
7.volatile关键字
可见性:对volatile声明的变量进行写操作时,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存,然后由于缓存一致性协议,其他处理器的会通过嗅探检测到自己缓存对应的内存地址被修改,就会将当前自己的缓存设置成失效
伪共享问题:
缓存行里不止一个变量,处理器a对变量a修改,那么处理器b要等处理器a提交缓存后,再从主存读取数据,最后才能对变量b进行修改
有序性:volatile读操作会在后面插入一个loadload和loadStore屏障,防止下面的普通读写操作与volatile读操作指令重排序;volatile写操作会在前面插入一个storestore屏障,防止前面普通写操作与volatile写操作重排序,会在后面插入一个storeload屏障,防止后面的volatile读写操作
8. ThreadLocal
ThreadLocal有一个静态内部类ThreadLocalMap。ThreadLocalMap为每一个线程维护了一个数组,数组下标为threadloca对象的hashcode与数组长度取模,值为变量的值;开放地址法解决hash冲突
内存泄漏问题:ThreadLocalMap对threadlocal是弱引用,对value是强引用,value就不会被GC,会造成内存泄漏,解决方法:threadlocal使用完后调用一下remove方***清掉key为null的方法)
9. 线程池
9.1 线程池的创建
ThreadPoolExecutor()
corePoolSize:线程池核心线程大小,最小同时运行的线程数量
CPU密集型:线程个数为CPU核数
IO密集型:线程个数为CPU核数的两倍
maximumPoolSize:线程池最大线程数量,超过就不再创建新线程
workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中:
A.ArrayBlockingQueue:数组实现的有界阻塞队列,按照先进先出的顺序进行排序
B.LinkedBlockingQueue:链表实现的阻塞队列,若其构造时指定大小是无界队列;不指定大小,其大小有Integer.MAX_VALUE来决定是无界队列。其所含的对象是先进先出顺序排序的。
C.PriorityBlockingQueue:支持优先级的无界阻塞队列,可以指定排序方法或采取自然顺序升序排列
D.SynchronizedQueue:不存储元素的阻塞队列,插入操作前必须等到另一个溢出操作
keepAliveTime:当线程池中的线程数量大于corePoolSize的时候,空闲线程等待销毁的时间;
unit : keepAliveTime 参数的时间单位。
threadFactory :创建新线程时使用的工厂,可用来设置线程名。
Handler:饱和策略
A. 丢弃任务,抛出异常(默认)
B. 丢弃任务
C. 丢弃队列中最早的任务
D. 用线程池所在线程执行任务
9.2 线程池的执行:
9.3 任务的提交
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功
9.4 线程池的关闭
逐个调用线程的interrupt方法:设置线程的中段标志位,等待线程被阻塞时抛出异常并退出阻塞状态
shutdown():正在执行的任务会继续执行下去,没有被执行的则中断
shutdownNow():正在执行的任务则被停止,没被执行任务的则返回
9.5 常见的线程池
1.FixedThreadPool
使用LinkedBlockingQueue无界队列,核心线程大小和 最大线程大小被设置为同一个值,运行中的FixedThreadPool不会拒绝任务,在任务比较多的时候会导致OOM(内存溢出)。
2.SingleThreadExecutor
使用无界队列(LinkedBlockingQueue),corePoolSize 和 maximumPoolSize 都被设置为 1,也会导致OOM
3.CachedThreadPool(缓存线程池)
使用不存储元素的阻塞队列(SynchronousQueue),corePoolSize 被设置为0,maximumPoolSize 被设置为 Integer.MAX.VALUE,先寻找空闲线程,有就把任务交给空闲线程执行,否则新建线程
4.newScheduledThreadPool
10.AQS(队列同步器)
有一个volatile的int变量表示同步状态值,还有一个双向队列完成资源获取线程的排队工作。
同步器包括一个头节点引用和一个尾节点引用;当前线程获取同步状态失败时,会被构造成一个节点加入队列中,并用CAS将尾节点引用指向节点。
1.ReentrantLock
可重入式:获取锁时会判断当前线程是否占据锁,然后增加同步状态值,释放锁时减小同步状态值,同步状态值为0时才彻底释放
非公平锁(默认):先CAS获取锁,获取失败再判断锁是否被释放,如果被释放就再次CAS获取锁,否则加入双向队列中。加入队列后会自旋判断前驱节点是不是首节点,然后获取同步状态
公平锁:如果当前是无锁状态,会先判断自己是否是头结点引用,如果是CAS获取锁,否则加入双向队列中;加入队列后会自旋判断前驱节点是不是首节点,然后获取同步状态
2.Semaphore(信号量)
初始化同步状态值,获取到资源时状态值减1,状态值为0时需要等待
3.countDownLatch
是通过一个计数器来实现的,计数器为自己设定的值,每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,等待的线程恢复运行;
4.CyclicBarrier(同步屏障)
在CyclicBarrier类的内部有一个计数器,每个线程在到达屏障点的时候都会调用await方法将自己阻塞,此时计数器会减1,当计数器减为0的时候所有因调用await方法而被阻塞的线程将被唤醒
11.乐观锁和悲观锁
乐观锁实现:版本号机制,CAS
乐观锁缺点:
1.ABA
2.循环时间长开销大
3.只能保证一个共享变量的原子操作
12.原子操作类
1.AtomicInteger:CAS+volatile修饰值保证可见性
2.AtomicReference(原子更新引用类型):CAS,解决了单个变量的原子操作问题
3.AtomicStampedReference(原子更新字段类):维护了一个对象值和版本号,解决了ABA问题
这篇关于对象面试官系列之Java并发--面试官看了都说好的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!