面试题-多线程IO并发编程
- 1. 问过的问题
- 2. java锁机制的实际运用
- 3. synchronized系列(对象锁)
- 4. volatile关键字
- 5. wait/notify/notifyall sleep yeild await/singnal/singnalAll的用法(生产消费模型中 obj.wait())
- 6. java 死锁是否会发生以及如何避免死锁
- 7. 创建线程的3种方式(Thread和Callable接口有返回值)和比较
- 8. Thread的一些常见方法
- 9. ReentrantLock API
- 10. 在Java中Lock接口比synchronized块的优势是什么?
- 11. JUC下的工具类(底层都是基于Lock的操作)
- 12. 线程池的使用
- 13. 线程池的关闭
问过的问题
锁
- java锁机制和运用–> 我说了 Socket编程当中IO流的 线程池使用,还有ConcurrentHashMap中的分段锁和volatile的使用,问CHMap中size()的实现原理(Done)
- String final类的实现原理
- Syncizaer的用法, Object.wait 和notify的使用,可不可以不再Syniczer中使用wait方法,实际不可以,会报错。以及java加锁的时候会不会出现死锁的情况,如何解决?(Done)
多线程:
- 创建线程的几种方式,那种最佳(根据业务场景,不复用的话 内部类、复用的话 Thread重写run方法 ,Runnable接口实现),实际上还有Future等等啊(Done)
- Tomcat的线程管理和如何控制并发,讲了一下JAVA的JUC下的线程池,构造函数,然后他问实际使用场景。
- JUC下的Executors框架熟悉吗? 不熟悉,那么数据库的线程池熟悉吗? 超过连接数如何处理
java锁机制的实际运用
- 阻塞队列,ArrayBlockingQueue一个锁、LinkedBlockingQueue两把锁
- ConcurrentHashMap中
- 生产和消费场景中,手动实现或者阻塞队列来实现
synchronized系列(对象锁)
synchronized的用法
- 修饰普通方法,相当于锁当前对象,调用者,也指 this对象
- 修饰静态方法,相当于锁当前类对象,也指 Test.class
- 修饰代码块,可以缩小锁的范围,提升性能
- 锁对象(this,类对象,普通对象)
注意事项:
- 锁普通方法和锁this的代码块,都是对象锁,锁静态方法和Test.class是类锁。
- 有锁方法和无锁方法互相不影响
- 类锁和对象锁互相不影响
- 释放锁的时候,修改的变量对所有线程可见,满足HB原则,从而实现线程安全
volatile关键字
公用的对象存放在主内存当中,每个线程去处理公用对象的时候会拷贝镜像到本地内存当中,在CPU进行读取,修改,写回到本地内存,最后写回到主内存当中,这时候线程之间不可见。volatile关键字会让读取和写的操作的时候,会立刻通知更新主内存同步,并不保证线程安全。
- 轻量级的同步机制,实现变量的改变对所有线程可见,并不能避免线程安全
- 每次读的时候去读主存上的值而不是本地栈中的值,每次写都写到主存当中
- 该方法中禁止指令重排序优化
wait/notify/notifyall sleep yeild await/singnal/singnalAll的用法(生产消费模型中 obj.wait())
- 三个方法都必须在synchronized 同步关键字所限定的作用域中调用,否则会报错java.lang.IllegalMonitorStateException
- 调用wait方法,释放对象所,阻塞当前线程,不会参与CPU的竞争,等待被notify()
当其他线程调用了wait方法时,该对象的锁被释放,被B线程获取之后,B执行各种操作之后,调用该对象的notify方法,但是不会立刻释放CPU资源,一直到该静态代码块执行完,然后再释放对象锁,A线程被唤醒,继续执行
obj.wait()
obj.notify() 仅通知一个obj的等待线程,结束时间是静态代码块执行完,而不是该线程执行完
obj.notifyall() 通知所有的obj的等待线程JUC里面有Lock替代synchronized,所以就需要有condition.await() 来实现object的wait()功能,否则实现不了线程通讯。
- Thread.sleep(1000); 使当前线程睡眠1秒钟,1秒钟后重新开始争夺CPU资源。
- Thread.yeild() hit调度器出让线程的争夺权,当然也不一定有效
java 死锁是否会发生以及如何避免死锁
- 不同线程同时争抢多个锁的时候,会导致死锁。
避免方法:
- 创建一个抢锁对象,比如Common.class,先获取到抢锁对象后,再进行多锁的方法操作,可以避免死锁的发生。
- 改用juc下面的定时锁,还有trylock,快速返回的,失败就释放当前锁,可以避免
创建线程的3种方式(Thread和Callable接口有返回值)和比较
- 线程类继承并重写 Thread的run方法,然后new出来.start()即可
- 实现runnable接口的类,作为new Thread(参数)的参数,调用start()即可
- A类实现Callable接口的call方法,然后作为FutureTask的构造参数,最后加入到线程池里面,或者Thread的构造参数里面启动,然后result.get()获取返回值
匿名内部类也有上面2种不同的实现Thread方式3种方式的比较
- runnable接口和callable接口的方式是以实现的方式,java是单继承,所以更好。
- 接口实现类作为target可以复用
- runnable有异步的返回值
Thread的一些常见方法
- join():父线程创建子线程,并start子线程后,调用 子线程.join()方法,可以阻塞父线程,直到子线程结束,这里面满足 HB原则
- yeild():尝试出让cpu的执行权
- wait():线程调度里面,在一个锁里面,调用 obj.wait方法,放弃当前线程的执行权,让其他线程开始争夺锁。wait方法必须在 锁里面才可以调用,不然报错。notify、notifyAll来唤醒
- sleep():阻塞当前线程,但是并不释放锁,一定时间后,重新争夺CPU执行权。
- interrupt() :并不阻塞当前线程,而是修改子线程的打断标志位,可用于线程之间的调度。如果线程不在运行状态,会报错。
ReentrantLock API
- lock(true,false):无条件地轮询获取锁的方式
- lockInterruptibly()提供了可中断的锁获取方式,争夺锁的过程时,会check 是否中断的状态位,如果true,就抛出异常
- trylock()、 tryLock(timeout),立刻返回结果,避免死锁
在Java中Lock接口比synchronized块的优势是什么?
提供轮询锁避免死锁、定时轮询锁、可中断的锁、锁分块技术CHashMap、公平锁等等高级灵活的功能。但是缺点是一定要在finally里面 unlock()资源才行。不然会一直在。
- 实现原理不同,synchronized基于JVM的实现,根据对象的消息头、唯一对应的monitor、线程栈桢中的minitor record来实现的,会根据锁竞争的程度不同,动态的升级 偏向锁、轻量级锁和重量级锁。而JUC下的Lock是基于JDK实现的,实现了更多的灵活的功能。
- 使用方法不同,synchronized的使用多种方式,修饰方法、修饰代码块,锁this对象、obj、类对象,而且无需手动释放,相对而言,面向开发人员更简约。Lock用法lock的API很多,unlock等操作。
- ReentrantLock实现了synchronized的公平队列功能,而Synch都是非公平锁
- ReentrantReadWriteLock类实现了读写锁的功能,而synchronized做不到
- ReentrantLock提供了trylock() 和 trylock(tryTimes)的功能,不等待或者限定时间等待获取锁,更灵活。可以避免死锁的发生。
- ReentrantLock提供了lockInterruptibly()的功能,可以中断争夺锁的操作
- ReentrantLock提供了比Sync更精准的线程调度工具,Condition,一个lock可以有多个Condition,但是Synizer只要一个WaitSet
JUC下的工具类(底层都是基于Lock的操作)
- BlockQueue:多种策略实现方式,解决有界的生产和消费队列问题
- CountDownLatch:2种方法配合,主要解决阻塞主线程,需要等待多个子线程都完成某一操作时,主线程再执行某一个操作,比如下载多sheet的文件。
- CyclicBarrier:1个方法,主要解决类似于集合的业务场景,指定数量的线程调用await方法时,所有的阻塞线程再同时执行某一操作,比如跑步开枪比赛,所有人到齐才开始跑。
- Semaphore:2个方法配合,主要解决有限资源无限竞争,比如多个人抢5个茅坑,一次最多5个人上一样,其它等待。
线程池的使用
5层继承关系:Executor顶层接口(execute方法) |ExcutorService接口类(submit、terminate) |AbstractExecutorService |ThreadPoolExecutor |ScheduledThreadPoolExecutor
ThreadPoolExcutor的7个核心参数(coresize、maxSize、BlockingQueue、Time、Union、ThreadFactory、抛弃策略)
Excutors类的常见的4个实现类(结合阻塞队列)
- FixedThreadPool(无界的LinkedBlockingQueue)
- SingleThreadPool(无界的LinkedBlockingQueue,不仅单个线程,而且是严格有序的执行)
- CachedThreadPool,同步线程池,coreSize=0,SynchronousQueue
- ScheduledThreadPool,构造函数用到ScheduledThreadPoolExecutor
- 线程池的管理:执行、关闭、执行状态查询和结果获取
- 两种执行方式:execute 不会返回线程执行是否成功,submit 返回一个future对象可以阻塞获取某线程的执行情况
- shutdown或shutdownNow方法来关闭线程池
- submit返回Future,查询线程执行情况:cancel()、isCancel()、isDone()、get()
线程池的关闭
shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不同,
- shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
- shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。