- 乐观锁 CAS(Compare And Swap)
- 悲观锁 synchronized、vector、hashtable
- 自旋锁 CAS
- 可重入锁 synchronized、Reentrantlock、Lock 递归锁
- 读写锁 ReentrantReadWriteLock、CopyOnWriteArrayList、CopyOnWriteArraySet
- 公平锁 Reentrantlock(true)
- 非公平锁 synchronized、Reentrantlock(false)
- 共享锁 ReentrantReadWriteLock中读锁
- 独占锁 synchronized、vector、hashtable、ReentrantReadWriteLock中写锁
- 重量级锁 synchronized
- 轻量级锁 锁优化技术
- 偏向锁 锁优化技术
- 分段锁 ConcurrentHashMap
- 互斥锁 synchronized
- 同步锁 synchronized
- 死锁 互相请求对方的资源
- 锁粗化 锁优化技术
- 锁消除 锁优化技术
- synchronized
- Lock与synchronized的区别
- ReentrantLock和synchronized的区别
乐观锁 CAS(Compare And Swap)
乐观锁是一种乐观思想,假定当前环境是读多写少,遇到并发写的概率比较低,读数据时认为别的线程不会正在进行修改操作(所以没有上锁)。写数据时,判断当前与期望值是否相同,如果相同则进行更新(更新期间加锁,保证原子性)。
Java中的乐观锁:CAS,比较并替换,比较当前值(主内存中的值),与预期值(当前线程中的值,主内存中值的一份拷贝)是否一样,一样则更新,否则继续进行CAS操作。
悲观锁 synchronized、vector、hashtable
悲观锁是一种悲观思想,即认为写多读少,遇到并发写的可能性高,每次去拿数据的时候都认为其他线程会发生修改操作,所以每次读写数据时都会上锁。其他线程如果想要读写这个数据时,会被这个线程block,直到这个线程释放锁然后其他线程才能获取到锁。
Java中的悲观锁:synchronized修饰的方法和方法块,ReentrantLock。
自旋锁 CAS
自旋锁是一种技术:为了让线程等待,我们只需让线程执行一个忙循环(自旋)。
优点:避免了线程切换的开销。挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给JVM的并发性能带来了很大的压力;
缺点:占用处理器的时间,如果占用时间过长,会白白消耗处理器资源,而不会做任何有价值的工作,带来性能浪费。因此自旋等待的时间必须有一定的限度,如果自旋超过限定次数仍然没有成功获取锁,就应当使用传统的方式挂起线程。
自旋次数默认值:10次,使用参数-XX:PreBlockSpin来修改;
自适应自旋:自适应意味着自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定,有了自适应自旋,随着程序运行时间的增长及性能监控信息的不断完善,VM对程序锁的状态会越来越精准。
Java中的自旋锁:CAS操作中的比较操作失败后的自旋等待。
可重入锁 synchronized、Reentrantlock、Lock 递归锁
可重入锁是一种技术:任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞;
原理:通过组合自定义同步器来实现锁的获取与释放。
再次获取锁:识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。获取锁后,进行计数自增,释放锁时,进行计数自减。
可重入锁的作用:避免死锁。
Java中的可重入锁:ReentrantLock、synchronized中修饰的方法或代码段;
读写锁 ReentrantReadWriteLock、CopyOnWriteArrayList、CopyOnWriteArraySet
读写锁是一种技术,通过ReentrantReadWriteLock类来实现。为了提高性能,Java提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。读写锁分为读锁与写锁,多个读锁不互斥,读锁与写锁互斥,由JVM控制。
读锁:允许多个线程获取读锁,同事访问同一个资源;
写锁:只允许一个线程获取写锁,不允许同时访问同一个资源。
Java中的读写锁:ReentrantReadWriteLock
公平锁 Reentrantlock(true)
公平锁是一种思想:多个线程按照申请锁的顺序来获取锁。在并发环境中,每个线程会先查看此锁维护的等待队列,如果当前等待队列为空,则占有锁,如果等待队列不为空,则加入到等待队列的末尾,按照FIFO的原则从队列中拿到线程,然后占有锁。
非公平锁 synchronized、Reentrantlock(false)
非公平锁是一种思想:线程尝试获取锁,如果获取不到,则再采用公平锁的方式。多个线程获取锁的顺序,不是按照先到先得的顺序,有可能后申请锁的线程比先申请的线程优先获取锁。
优点:非公平锁的性能高于公平锁;
缺点:有可能造成线程饥饿(某个线程很长一段时间获取不到锁);
Java中的非公平锁:synchronized是非公平锁,ReentrantLock通过构造函数指定锁是公平锁还是非公平锁,默认是非公平锁。
共享锁 ReentrantReadWriteLock中读锁
共享锁是一种思想:可以有多个线程获取读锁,以共享的方式持有锁。和乐观锁、读写锁同义。
Java中的共享锁:ReentrantReadWriteLock。
独占锁 synchronized、vector、hashtable、ReentrantReadWriteLock中写锁
独占锁是一种思想:只有一个线程获取锁,以独占的方式持有锁。和悲观锁、互斥锁同义。
Java中的独占锁:synchronized、ReentrantLock。
重量级锁 synchronized
重量级锁是一种称谓,synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本身依赖底层的操作系统(Operation System OS)的Mutex Lock来实现。OS实现线程的切换需要从用户态切换到核心态,成本非常高。这种依赖于OS Mutex Lock 来实现的锁称为重量级锁。为了优化synchronized,引入了轻量级锁,偏向锁。
Java中的重量级锁:synchronized
轻量级锁 锁优化技术
轻量级锁是JDK6时加入的一种锁优化机制:轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量。轻量级是你想对于使用OS互斥量来实现的重量级锁而言的。轻量级锁在没有多线程竞争的前提下,减少传统的重量级锁使用OS互斥量产生的性能消耗。如果出现两条以上的线程争用同一个锁的情况下,那轻量级锁将不会有效,必须膨胀为重量级锁。
优点:如果没有竞争,通过CAS操作成功避免了使用互斥量的开销;
缺点:如果存在竞争,除了互斥量本身的开销外,还额外产生了CAS操作的开销,因此在有竞争的情况下,轻量级锁比传统的重量级锁更慢。
偏向锁 锁优化技术
偏向锁是JDK6时加入的一种锁优化机制:在无竞争的情况下把整个同步都消除掉,连CAS操作都不去做了。偏是指偏心,他的意思是这个锁会偏向于第一个获取它的线程,如果在接下来的执行过程中,该锁一直没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作(例如加锁、解锁及对mark Word的更新操作等)。
优点:把整个同步都消除掉,连CAS操作都不去做了,优于轻量级锁。
缺点:如果程序中大多数的锁都总数被多个不同的线程访问,那偏向锁就是多余的。
分段锁 ConcurrentHashMap
分段锁是一种机制:最好的例子来说明分段锁是ConcurrentHashMap。
ConcurrentHashMap原理:它内部细分为若干个小的HashMap,称之为段(segment)。默认情况下一个ConcurrentHashMap被进一步细化分为16个段,即这就是锁的并发度。如果需要在ConcurrentHashMap添加一项key-value,并不是将整个HashMap加锁,而是首先根据hashcode得到该key-value应该存在哪一段,然后对该段加锁,并完成put操作。在多线程环境下,如果多个线程同时进行put操作,只要被加入的key-value不存放在同一个段中,则线程间可以做好真正的并行。
线程安全:ConcurrentHashMap是一个Segment数组,Segment通过集成ReentrantLock来进行加锁,所以每次需要加锁的操作锁住的是一个segment,这样只需保证每个Segment是线程安全的,也就是实现了全局的线程安全。
互斥锁 synchronized
互斥锁与悲观锁、独占锁同义,表示某个资源只能被一个线程访问,其他线程不能访问。
读-读互斥
读-写互斥
写-写互斥
写-读互斥
Java中的同步锁:synchronized
同步锁 synchronized
同步锁与互斥锁同义,表示并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。
Java中的同步锁:synchronized
死锁 互相请求对方的资源
死锁是一种现象:如线程A持有资源x,线程B持有资源y,线程A等待线程B释放资源y,线程B等待线程A释放资源x,两个线程都不释放自己只有的资源,则两个线程都获取不到对方的资源,就会造成死锁。
Java中的死锁不能自行打破,所以线程死锁后,线程不能进行响应。所以一定要注意程序的并发场景,避免造成死锁。
p
ackage com.myself.learn.lock;
import java.util.concurrent.TimeUnit;
/**
* @author zhangxiaofan
* @date 2021/12/18 11:29
*/
public class DeathLock {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread( new MyThread( lockA , lockB ) , "T1" ).start();
new Thread( new MyThread( lockB , lockA ) , "T2" ).start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread( String lockA , String lockB ) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized ( lockA ){
System.out.println( Thread.currentThread().getName() + "lock:" + lockA + ",want to get " + lockB );
try {
TimeUnit.SECONDS.sleep( 3 );
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ( lockB ){
System.out.println( Thread.currentThread().getName() + "lock:" + lockB + ",want to get " + lockA );
}
}
}
}
死锁问题排查
查日志
使用jps -l 查看定位进场号,jstack + 进程号 排查 tips:服务在运行中使用
jps -l信息
10112 com.myself.learn.lock.DeathLock
1936 sun.tools.jps.Jps
3040 org.jetbrains.idea.maven.server.RemoteMavenServer36
12828
9868 org.jetbrains.jps.cmdline.Launcher
jstack信息
2021-12-18 11:36:18
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.261-b12 mixed mode):
"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000015383bc9800 nid=0x5f0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"T2" #13 prio=5 os_prio=0 tid=0x00000153a0aa8000 nid=0x1da0 waiting for monitor entry [0x00000051453ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.myself.learn.lock.MyThread.run(DeathLock.java:40)
- waiting to lock <0x000000076c3e1610> (a java.lang.String)
- locked <0x000000076c3e1648> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"T1" #12 prio=5 os_prio=0 tid=0x00000153a0aa7800 nid=0x990 waiting for monitor entry [0x00000051452fe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.myself.learn.lock.MyThread.run(DeathLock.java:40)
- waiting to lock <0x000000076c3e1648> (a java.lang.String)
- locked <0x000000076c3e1610> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x00000153a08a9800 nid=0xccc runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x00000153a08a1000 nid=0x3010 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000153a089e000 nid=0x1bec waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x00000153a089b800 nid=0x1ec4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x00000153a089a800 nid=0x333c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x00000153a0899000 nid=0x34b0 runnable [0x0000005144bfe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076c2cfc28> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x000000076c2cfc28> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:48)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000001539e8d3000 nid=0x3154 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000001539e8d2000 nid=0x3ac runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000001539e84b000 nid=0xd7c in Object.wait() [0x00000051448ff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076c008ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x000000076c008ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000001539e83a000 nid=0x35f0 in Object.wait() [0x00000051447ff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076c006c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076c006c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x000001539e812800 nid=0x6f8 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000015383be0800 nid=0x2158 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000015383be1800 nid=0x688 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000015383be3000 nid=0x230c runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000015383be4800 nid=0x191c runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000015383be6800 nid=0x3620 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000015383be7800 nid=0x2098 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000015383bea800 nid=0x2720 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000015383beb800 nid=0x1c48 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x00000153a0a49000 nid=0x3024 waiting on condition
JNI global references: 12
Found one Java-level deadlock:
=============================
"T2":
waiting to lock monitor 0x000001539e84a6f8 (object 0x000000076c3e1610, a java.lang.String),
which is held by "T1"
"T1":
waiting to lock monitor 0x000001539e847db8 (object 0x000000076c3e1648, a java.lang.String),
which is held by "T2"
Java stack information for the threads listed above:
===================================================
"T2":
at com.myself.learn.lock.MyThread.run(DeathLock.java:40)
- waiting to lock <0x000000076c3e1610> (a java.lang.String)
- locked <0x000000076c3e1648> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"T1":
at com.myself.learn.lock.MyThread.run(DeathLock.java:40)
- waiting to lock <0x000000076c3e1648> (a java.lang.String)
- locked <0x000000076c3e1610> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
锁粗化 锁优化技术
锁粗化是一种优化技术:如果一系列的连续操作都对同一个对象进行反复加锁和解锁,甚至加锁操作都是出现在循环体中,就算真的没有现成竞争,频繁的进行互斥同步操作将会导致不必要的性能消耗,所以就采取了一种方案,把加锁的范围扩展(粗化)到整个操作序列的外部,这样加锁解锁的频率就会大大降低,从而减少性能消耗。
锁消除 锁优化技术
锁消除是一种优化技术:就是把锁干掉。当JVM运行时发现有些共享数据不会被线程竞争是就进行锁消除。
如何判断共享数据不会被线程竞争?
利用逃逸分析技术:分析对象的作用于,如果对象在A方法中定义后,被作为参数传到B方法中,则称为方法逃逸;如果被其他线程访问,则称为线程逃逸。
在堆中的某个数据不会逃逸出去被其他线程访问到,就可以把它当做栈上数据对的,认为它是线程私有的,同步加锁就不需要了。
synchronized
synchronized是Java中的关键字:用来修饰方法,对象实例。属于独占锁、悲观锁、可重入锁、非公平锁。
- 作用于实例方法时,锁住的是对象的实例(this);
- 当作用于静态方法时,锁住的Class类,相当于类的一个全局锁,会锁所有调用该方法的线程;
- synchronized作用于一个非NULL的对象实例时,锁住的是所有以该对象为锁的代码块。它由多个队列,当多个线程一起访问某个对象监视器时,对象监视器会将这些线程存储在不同的容器中。
每个对象都有个monitor对象,加锁就是在竞争monitor对象,代码块加锁是在代码块前后分别加上monitorenter和monitorexit指令来实现的,方法加锁是通过一个标记为来判断的 。
Lock与synchronized的区别
Lock:Java中的接口,可重入锁、悲观锁、独占锁、互斥锁、同步锁
- Lock需要手动获取锁和释放锁;
- Lock是一个接口,synchronized是Java中的关键字,synchronized是内置的语言实现;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized不行;
- Lock可以通过实现读写锁提高多个线程进行读操作的效率。
synchronized的优势:足够清晰简单,只需要基础的同步功能时,使用synchronized。
Lock必须在finally块中释放锁。如果使用synchronized,JVM确保即使出现异常,锁也能被自动释放,使用Lock时,JVM很难得到哪些锁对象由哪些特定线程持有。
ReentrantLock和synchronized的区别
ReentrantLock是Java类:继承了Lock类,可重入锁、悲观锁、独占锁、互斥锁、同步锁;
相同点:
- 解决共享变量如何安全访问的问题;
- 都是可重入锁,也叫作递归锁,同一个线程可以多次获得同一个锁;
- 保证线程安全的两大特性:可见性、原子性。
不同点:
- ReentrantLock需要手动调用lock()和unlock()方法,synchronized隐式获得释放锁;
- ReentrantLock可响应中断,synchronized不可响应中断,ReentrantLock为处理锁的不可用性提供了更高的灵活性;
- ReentrantLock是API级别的,synchronized是JVM级别的;
- ReentrantLock可以实现公平锁、非公平锁、默认是非公平锁,synchronized是非公平锁,且不可更改;
- ReentrantLock通过Condition可以绑定多个条件。