并发专题
线程状态
初始状态(NEW)
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。
运行状态(RUNNABLE)
Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)
阻塞状态(BLOCKED)
当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间
当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态
等待(WAITING)
处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
超时等待(TIMED_WAITING)
当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间
当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁
如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁
还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态
终止状态(TERMINATED)
当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
线程池的核心参数
核心线程:当线程执行完任务以后,仍然需要保留在线程中
救急线程:当线程执行完任务以后,不需要保留在线程中
- corePoolSize:核心线程数目(池中会保留的最多线程数)
- maximumPoolSize:最大线程数目(核心线程+救急线程的最大数目)
- keepAliveTime:生存时间(救急线程的生存时间,生存时间内没有新任务,此线程资源会释放)
- unit:时间单位(救急线程的生存时间单位,如秒、毫秒等)
- workQueue:当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
- threadFactory:线程工厂(可以定制线程对象的创建,例如设置线程名字、是否是守护线程等)
- handler:拒绝策略(当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略))
wait和sleep
共同点
- wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
不同点
方法归属不同
- sleep(long) 是 Thread 的静态方法
- 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
醒来时机不同
执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
它们都可以被打断唤醒
锁特性不同(重点)
- wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
- wait 方法执行后会释放对象锁,允许其它线程获得该对象锁
- 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁
lock和synchronized
语法层面
synchronized是关键字,源码在JVM中,用C++实现
lock是接口,源码由jdk提供,用java语言实现
使用synchronized时,退出同步代码块锁会自动释放,而使用Lock时,需要手动调用 unlock 方法释放锁
功能层面
二者都属于悲观锁,都具备基本的互斥,同步,锁重入
Lock提供了许多synchronized不具备的功能,如:获取等待状态,公平锁,可打断,可超时,多条件变量
Lock有适合不同场景的实现,如:ReentrantLock,ReentrantReadWriteLock
性能层面(*)
在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖
在竞争激烈时,Lock 的实现通常会提供更好的性能
公平锁与非公平锁
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁
优点:所有的线程都能得到资源,不会饿死在队列中
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,而cpu唤醒阻塞线程的开销大
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死
可以通过对构造器传值来决定使用哪种锁
1 | ReentrantLock lock = new ReentrantLock(true); |
注意:
tryLock带参数的方法,会根据ReentrantLock设置的true或false实现公平或非公平锁
tryLock无参的方法,则是默认使用了非公平锁
RentrantLook
Sync类是ReentrantLock他本身的一个内部类,它继承了AbstractQueuedSynchronizer,在操作锁的大部分操作,都是Sync本身去实现的
Sync分别有两个子类:FairSync和NofairSync,分别体现了公平锁与非公平锁
公平锁的实现与否主要是这个hasQueuedThreads方法
判断当前的线程是不是位于同步队列的首位,是就是返回true,否就返回false
volatile
线程安全要考虑三个方面:可见性、有序性、原子性
可见性:一个线程对共享变量修改,另一个线程能看到最新的结果
有序性:一个线程内代码按编写顺序执行
原子性:一个线程内多行代码以一个整体运行,期间不能有其他线程的代码插队
volatile只能保证可见性和有序性,不能保证原子性
解决可见性问题
起因:由于编译器优化、或缓存优化、或 CPU 指令重排序优化导致的对共享变量所做的修改另外的线程看不到
解决:用 volatile 修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见
解决有序性问题
起因:由于编译器优化、或缓存优化、或 CPU 指令重排序优化导致指令的实际执行顺序与编写顺序不一致
解决:使用volatile修饰变量,会在读或写时候加入不同的屏障,避免读或者写的操作越过屏障,从而达到阻止重排序的效果
原子性问题
起因:多线程下,不同线程的指令发生了交错导致的共享变量的读写混乱
解决:用悲观锁或乐观锁解决,volatile 并不能解决原子性
悲观锁
悲观锁的代表是 synchronized 和 Lock 锁
核心思想:线程只有占有了锁,才能去操作共享变量,每次只有一个线程占锁成功。任何获取锁失败的线程,都得停下来等待
缺点:线程从运行到阻塞,从阻塞到唤醒,涉及线程上下文切换,如果频繁发生,影响性能
实际上,线程在获取synchronized 和 Lock 锁时,如果锁已经被占用,都会做几次重试操作,减少阻塞的机会
乐观锁
乐观锁的代表是 AtomicInteger,使用CAS来保证原子性
核心思想:无需加锁,每次只有一个线程能成功修改共享变量,其他失败的线程不需要停止,不断重试直到成功
缺点:需要多核CPU支持,且线程数不应超过CPU核数
- hashmap和hashtable区别
- 为什么产生死锁
- jvm类加载
- java反射获取私有属性,改变值
- 反射用途
- JVM内存模型
- 垃圾回收机制
- 项目中查看垃圾回收
- ConcurrentHashMap底层原理
- HashMap底层数据结构
- JDK1.8中的HashMap为什么用红黑树不用普通的AVL树?
- 为什么在JDK8的时候链表变成树?
- 线程池7个参数,该怎么配置最好?
- 说一下volatile
- volatile的可见性和禁止指令重排序怎么实现的?
- CAS是什么
- PriorityQueue底层是什么,初始容量是多少,扩容方式呢?
- HashMap的容量为什么要设置为2的次幂?
- CopyOnWriteArrayList知道吗,迭代器支持fail-fast吗?
- synchronized关键字的用法
- synchronized修饰类方法和普通方法的锁区别,获取类锁之后还能获取对象锁吗
- 类加载器的双亲委派模型的作用,能重复加载某个类吗
- 类加载器的类的缓存,key是什么
- 字节码结构
- HashMap在大量哈希冲突该怎么处理
- HashMap查找效率
- 讲一下线程池,以及实现固定大小线程池底层是如何实现的?
- 堆内存和栈内存有什么区别
- ThreadLocal的使用场景
- hashmap结构;什么对象能做为key
- hashtable,concurrentHashMap,hashtable比较
- wait,sleep分别是谁的方法,区别
- countLatch的await方法是否安全,怎么改造
- 线程池参数,整个流程描述 ,背后的底层原理aqs,cas
- 对Runtime的了解
- 什么情况会造成内存泄漏
- 新生代分为几个区?使用什么算法进行垃圾回收?为什么使用这个算法?
- 如何解决同时存在的对象创建和对象回收问题
- 什么是活锁、饥饿、无锁、死锁?怎么检测一个线程是否拥有锁?
- 为什么 Java 要采用垃圾回收机制,而不采用 C/C++的显式内存管理?
- 一个线程的生命周期有哪几种状态?它们之间如何流转的?
- Java容器有哪些?哪些是同步容器,哪些是并发容器?
- 讲一讲AtomicInteger,为什么要用CAS而不是synchronized?
- java中的线程有几种状态?详细说明
- Java 初始化一个线程池有哪些参数可以配置, 分别是什么作用?
- 什么对象会从新生代晋升到老年代
- HashMap ConcurrentHashMap的区别?
- (Java)下面聊一下java,集合用过哪些,源码看过哪些
- (Java)hashmap详细说一下
- (Java)hashmap的扩容为什么是2倍(告诉我和hash算法有关)
- (Java)ConcurrentHashMap说一下,怎么实现线程安全的,分段锁是怎么实现的(没回答出来,面试官告诉我AQS)
- (Java)说一下AQS
- (Java)synchronized说一下,我说了使用场景,实现原理,优化
- (Java)volatile说一下
- (Jvm)JVM内存结构说一下,堆栈方法区这些,我问了面试官一个问题,为什么叫方法区,面试官说没有思考过
- (Jvm)J说一下堆和栈
- (Jvm)J类加载机制
- (Jvm)J垃圾回收原理,原理,算法,cms和G1的区别
- (Jvm)JStackOverFlow有遇到过吗?说一下
- (Jvm)JOOM有遇到过吗,说一下,怎么分析OOM
- (Java)什么是面向对象,结合面向过程进行了对比
- (Java)StringBuffer和StringBuilder的区别说一下
- (Java)ArrayList和LinkList的区别说一下,插入的时间复杂度
- CAS原理说一下?
- 多线程都有哪些锁?
- synchronized和lock区别?
- jvm内存模型
- GC机制
- 类加载机制
- 双亲委派模型
- (Java)Object用到了哪些方法
- (Java)Object线程同步的相关方法
- (Java)说说对线程的理解
- (Java)java中如何创建线程
- (Java)java如何实现线程的
- (Java)synchronized和volatile区别,二者的实现原理
- (Java)synchronized修饰静态变量和非静态变量的区别
- jdk的动态代理是怎么实现的
- (Java)说说类加载器,双亲委派了解吗?
- (Java) 说说对JVM的了解
- (Java) 为什么说Java是跨平台的?
- (Java) JVM的组成?每一块的作用?
- (Java) 垃圾回收GC分代和GC算法
- (Java) 什么样的对象会在堆里?
- (Java) ReentrantLock的特点?
- (Java) 公平锁非公平锁?
- (Java) AQS的实现原理,包括其中的cas,cas是怎么实现的,有什么好处?
- (Java) Hashmap的实现原理?
- (Java) 线程安全的问题
- (Java)hashmap是怎么实现的
- (Java)ConcurrentHashMap和Hashmap的区别
- (Java)hashmap冲突怎么办
- (Java)java的深拷贝和浅拷贝
- hashmap 和 hashtable 的区别说一下
- hashtable 怎么保证线程安全的?
- synchronized 是怎么实现的,监视器锁是怎么实现的
- ConcurrentHashMap 是怎么实现的?CAS 的问题是什么。CAS适用哪种场景。
- 如何解决 hash 的碰撞问题
- hashmap 的扩容说一下,怎么实现的。
- hashtable 的扩容是线程安全的吗