测试上下文切换次数和时长
工具可以度量上下文切换带来的消耗:
- 使用Lmbench3可以测量上下文切换的时长。( Lmbench3是一个性能分析工具**)**
- 使用vmstat可以测量上下文切换的次数。
如何减少上下文切换
减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。
- 无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。
- CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
- 使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。
- 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
减少上下文切换实战
统计所有线程分别处于什么状态
grpe awk stort unique-c
死锁( 死锁面试题(史上最强) - 疯狂创客圈 - 博客园 (cnblogs.com))
什么是死锁?
- 所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。
产生死锁的原因?
- a. 竞争资源
系统中的资源可以分为两类:
可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁 - b. 进程间推进顺序非法
若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁
死锁产生的4个必要条件
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
解决死锁的基本方法
- 预防死锁
- 避免死锁
- 检测死锁
- 解除死锁
具体见连接: 死锁面试题(史上最强) - 疯狂创客圈 - 博客园 (cnblogs.com)
synchronized使用详解( Java多线程------synchronized使用详解_synchronized用法-CSDN博客/ 面试官:请详细说下synchronized的实现原理 - 知乎 (zhihu.com))
计算机里面的指令重排
- 是计算机组成原理里边流水线技术导致的,流水线技术能增大整体的运行速度,但是会导致指令重排序,导致多线程下的并发问题
写后读
- 一个线程等读其他线程写之后再去读取内容,然后再去写操作
- 一个加锁的方法,在多线程调用时,只允许一个线程对他进行拷贝入栈,其他线程无法进行
类锁( Java中synchronized实现类锁的两种方式及原理解析_synchronized(.class)-CSDN博客)
类锁有两种实现方式:
- synchronized加在static方法上(静态方法锁)。
- synchronized(*.class)代码块
CAS(比较并且交换)
可见性
- 指的是能即使知道其他线程对共享变量做出的修改,其中一个线程对共享变量做出操作的时候另外一个线程能准确地读取到这种变化
原子操作
- 大白话:假设一件事,假设有10个步骤啊,这10个步骤我都成功了,要么就是说我这10个步骤里边,我可能执行到第7个步骤的时候,第7个步骤失败了,失败怎么办呢,前6个步骤还原,还原的话相当于都失败了。要么都成功,要么就都失败,要出现一个失败,大家都得失败,都得还原。
Java如何实现原子操作
面试题 :java中那些类是线程安全的?( Java常见的线程安全的类-CSDN博客 )(其中线程安全的类是指我们在并发编程时调用这个类不需要自己加锁,它自身就有,而线程不安全的类我们就需要自己写锁来 保证多线程安全)
- 通过synchronized 关键字给方法加上内置锁来实现线程安全
Timer,TimerTask,Vector,Stack,HashTable,StringBuff - 原子类 Atomicxxx---包装类的线程安全类
如AtomicLong,AtomicInteger等等 Atomicxxx 是通过Unsafe 类的native方法实现线程安全的 - Concurrentxxx
最常用的就是ConcurrentHashMap,当然还有 ConcurrentSkipListSet 和 ConcurrentSkipListMap 等等。
ABA问题**(面试点)(面试|详解CAS及其引发的三个问题-腾讯云开发者社区-腾讯云 (tencent.com))**
ABA问题是指在CAS操作时,其他线程将变量值A改为了B,但是又被改回了A,等到本线程使用期望值A与当前变量进行比较时,发现变量A没有变,于是CAS就将A值进行了交换操作,但是实际上该值已经被其他线程改变过,这与乐观锁的设计思想不符合。
解决方法:ABA问题的解决思路是,每次变量更新的时候把变量的版本号加1,那么A-B-A就会变成A1-B2-A3,只要变量被某一线程修改过,改变量对应的版本号就会发生递增变化,从而解决了ABA问题。
synchronized的实现原理与应用
synchronized的****实现原理:
从JVM规范中可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。但是,方法的同步同样可以使用这两个指令来实现。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。synchronized用的锁是存在Java对象头里的。
类锁、对象锁( 类锁和对象锁的区别,面试重点题型 - 知乎 (zhihu.com)、 类锁和对象锁的详解-CSDN博客)
Java对象头
synchronized用的锁是存在Java对象头里的。
红框框用于存放锁信息,其中电脑是32位的就是32bit,64位的就是64bit
锁的升级与对比( 锁的升级是不可逆的!!只能从轻量到重量**)**
- 我们一个线程对一个资源加了锁,如果这个这个时间片到期了,整个程序还没执行完,它即便回到就绪队列,这个锁依然存在。
- 对线程竞争加锁失败,进入阻塞队列,保证cpu的运转效率,这就是阻塞队列作用。
偏向锁
- hotspot是的是java运行的区域。
- CAS比较交换:因为在对象头出的锁信息有8字节(64bit),那么cou一次无法读取完,无法做出完整的改变,至少需要两次读取,那么可能会导致A线程加锁时加了一半,B线程发现锁只有一半,认为没有加锁,会进行加锁操作,而此时A线程又回来加下一半锁,导致出现问题。CAS比较交换就是指线程在加锁前,先对对象头中的锁信息进行比较,符合就继续交换,不符合就停止交换。这就是为什么更新对象头需要用到CAS,因为大字节总线一次无法更新完。在计算机底层,很多的更新都是需要用到CAS,至关重要。
轻量级锁
特点就是其他线程一旦释放锁,它能以最快的速度感知到锁的变化,然后立刻加锁。比如,当t1释放锁后,t2会第一时间感知到,然后进行加锁,t2不会进入阻塞队列。
自旋就是指死循环,不断的尝试替换对象头部的指针。t2自旋,死循环不断尝试用CAS替换对象头的锁信息,当t1释放锁后,马上加锁。
重量级锁
自旋会消耗CPU,为了避免无用的自旋,轻量级锁会晋升为重量级锁。比如这种情况,当一个线程加锁时,存在很多其他线程并发自旋,浪费大量cpu,此时轻量级锁会晋升为重量级锁,将其他线程全部进入阻塞队列,降低cpu的损耗,使cpu全力进行当前线程。
各个锁的优点和缺点
final的作用
- 阻止类的继承
- 阻止方法的重写
- 阻止基本类型的第二次复制
- 阻止一种类型的第二次重新指向
- 防止指令重排序,保证多线程的可见性(和volatile相比更加轻一些,速度会更快一些,但功能不如volatile全)
双重检查锁定与延迟初始化( 面试点 )
双重检查锁属于设计模式中的单例模式,单例模式中的懒加载模式。
单例模式是指一个类只有一个对象,实现方法:( java单例模式------详解JAVA单例模式及8种实现方式_单例模式java实现-CSDN博客 )
- 枚举,缺点是对象是一开始就存在,当类加载到内存时,对象已经存在了,会对内存占用较大。
- 双重检查锁,缺点是代码略复杂
++下面代码具体详细解释++ , 很重要,面试重点
java内存模型
volatile的应用
- volatile是轻量级的synchronized,它只能保证读的正确,不能保证写的正确;同时如果没有它,则会因为指令重排序,导致读取错误。
- volatile 的作用就是防止指令重排序
- volatile防止指令重排序,只能保证它的上一句和他的下一句,其他的代码不受影响,同时这一点也需要看具体情况
ReentrantLock( 面试点**)**
是一种锁,它和 synchroniz的区别就是,它可以 手动的给它加锁和解锁来确定锁的范围,而synchroniz是由操作系统决定的。它是可以有我们自己定义何时加锁,合适释放锁的。比synchroniz更加灵活。
AQS( Java同步器框架AbstractQueuedSynchronizer )( 面试点 )
ReentrantLock的实现依赖于AQS,同时 synchroniz也依赖于它。锁的底层实现是靠AQS的。
包含三个类:state,acquire(加锁),release(释放锁)
公平锁和非公平锁
非公平锁性能更好,原因跟操作系统的原因是一样的( 图解:为什么非公平锁的性能更高?-腾讯云开发者社区-腾讯云 (tencent.com) )
什么是锁的重入?( 到底什么是重入锁,拜托,一次搞清楚! - 知乎 (zhihu.com))( 面试点 )
线程之间如何通信?进程之间如何通信?( 面试点 )
进程之间的通信,线程都包含,因为进程是有线程组成的。
线程之间通信有很多手段,比如说共享变量,调方法直接通知,借助端口,借助硬盘等等很多,一切能想到的能把这个线程里边的信息发给那个线程的都是线程间通信的手段,最常用的还是共享内存,就是说共同操作一个变量。
进程之间的通信方式有两种,一个是共享内存和消息传递,消息传递具体有很多类。
什么是线程
线程本质上在操作系统内部它是一个数组形成的栈,然后栈的压栈出栈的元素就是调用的方法,方法调用是被拷贝一份压栈,然后方法执行完毕就是出栈
创建线程的方式( java创建线程(Thread)的5种方式_java new thread 方法-CSDN博客)
- 继承于Thread类
- 实现Runnable接口
- 实现Callable接口
- 使用线程池
- 使用匿名类
线程的状态
- 新建,就绪,运行,等待,阻塞,死亡
- 进入等待的线程,如果没人来唤醒它,它会一直在等待队列中,不会回归就绪状态,不会再执行
等待(wait)和sleep的区别
- sleep是让出CPU不需要锁,就是sleep只是让出CPU,所以呢,在执行sleep的时候提前加不加锁无所谓。但是wait不一样,wait让出CPU,并且释放锁,所以一定得事先持有锁,一定得事先持有锁。所以这一块呢,调wait的时候事先持有锁。实现对这个资源持有锁,这个资源,然后呢调用它的wait
- sleep(0)是立即让出cpu的意思
如何查看线程信息
- 关键指令gps就可以了
线程的中断
- 中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。
- 线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。
线程间通信、进程间通信
- 线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一步一步地执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立地运行,那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作,这将会带来巨大的价值
- 线程间通信:由于多线程共享地址空间和数据空间,所以多个线程间的通信是一个线程的数据可以直接提供给其他线程使用,而不必通过操作系统(也就是内核的调度)。
- 进程间的通信则不同,它的数据空间的独立性决定了它的通信相对比较复杂,需要通过操作系统。
Thread.join()的使用
如何控制线程之间的执行顺序
通过join()来控制线程的执行顺序
ThreadLocal的使用( 面试题 - ThreadLocal详解_threadlocal面试题-CSDN博客)
threadlocal的实现原理就是每个线程调用它的时候都会拷贝一份,把数据存进去,所以每个线程只能从自己的拷贝体,里边去对他进行读写操作。
threadlocal里面运用了虚引用。
线程如何同时开始?( 面试点 )( 大厂高频面试题------如何完全同时启动两个线程?_两个线程同时进行-CSDN博客)
一个是cut down launch,一个是自己手写实现,一个是使用CyclicBarrier 类
线程池
创建第一个线程之后,第一线程已经执行完第一个任务了,第一个线程已经存在了,并且属于控制状态,我又提交了一个任务,它不会交给第一个线程控制的状态,它不会交给控制线程去执行他,他会创建第二个线程( 面试点 )
包含的方法( 面试点 )
- void execute(Job job); // 关闭线程池
- void shutdown(); // 增加工作者线程
- void addWorkers(int num); // 减少工作者线程
- void removeWorker(int num); // 得到正在等待执行的任务数量
线程池的提交方式有哪些?有什么区别?( 面试点 )
- 调用execute ()方法,不带返回值
- 调用submit ()方法,带返回值
读写锁
适用于读多写少的场景,学它的原因是因为大多数场景下,读多写少。
ReentrantReadWriteLock的特性( 面试点**)**
- 支持非公平(默认)和公平
- 支持重进入
- 支持锁降级
代码样例:( 面试点 )( 面试时可能会让手写一个缓存代码,下面这个就可以,加大分!!! )
读写锁降级的过程中是否释放锁?( 面试点 )
不释放锁,锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。
condition接口( 面试点 )
Java 并发编程面试题------Condition 接口-CSDN博客
Java 并发编程面试题------Condition 接口 - 技术栈 (jishuzhan.net)
注意看讲解,自己组织语言
Java并发容器和框架
ConcurrentHashMap的实现原理与使用( 面试必考!!! )
面试题具体:
- hashmap,hashtable,ConcurrentHashMap作对比
hashmap线程不安全;hashtable是整体加 synchroniz锁,性能慢; ConcurrentHashMap是分成了很多个子hashmap,然后在每个里面单独加锁,并且加锁是加的读写锁,性能高。所以hashmap线程不安全,后两者线程安全,但hashtable性能慢。 - ConcurrentHashMap实现原理
ConcurrentHashMap 面试题_cocurrenthahsmap 面试题-CSDN博客 - hashmap实现原理
【面试题】HashMap 底层实现原理是什么?JDK8 做了哪些优化?_哈希防盗链实现原理-CSDN博客 - ConcurrentHashMap实现原理(jdk1.7和1.8)
ConcurrentHashMap 面试题_cocurrenthahsmap 面试题-CSDN博客 - hashmap实现原理(jdk1.7和1.8),并且hashmap的扩容会导致哪些问题
【面试题】HashMap 底层实现原理是什么?JDK8 做了哪些优化?_哈希防盗链实现原理-CSDN博客
两万字最全HashMap面试题总结,持续更新中 - 知乎 (zhihu.com)
如何使用?
Java并发编程------ConcurrentHashMap详解_java concurrenthashmap-CSDN博客
散列
hash,就是散列。
Java中的阻塞队列( 面试点 )
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。