1.公平锁 / 非公平锁
1、公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
- 优点:所有的线程都能得到资源,不会饿死在队列中。
- 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
2、非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
- 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
- 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
2.可重入锁(递归琐) / 不可重入锁
3.独享锁 / 共享锁
1、独占锁:每次只能允许一个节点获取到资源,每次释放资源之后也只会唤醒后驱节点(Reentrantlock)
2、共享锁:每次可以允许多个节点按照顺序获取资源,每次释放头节点资源后可能会唤醒后驱的后驱。(CountDownLatch,ReadWriteLock)(下方讲实现的时候有解释)
4.互斥锁 / 读写锁
5.乐观锁 / 悲观锁
6.分段锁
7.偏向锁 / 轻量级锁 / 重量级锁(互斥锁)
8.自旋锁
synchronized特点:
- 具有可重入、非公平、独占、不可中断等特性。
- 根据实际运行情况,锁将会膨胀,经历从偏向锁、轻量级锁、重量级锁的膨胀过程。
- 只能膨胀、不能收缩。偏向锁和轻量锁为乐观锁,重量级锁为悲观锁。
- 它的优化、锁的申请、释放与分配都是自动的。开发者可以快捷使用。
常用的作用有三个:
原子性:
即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行;可见性:
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值;有序性:
防止编译器和处理对指令进行重排序,即也就是抑制指令重排序;
synchronized的理解指导文章:
1、https://mp.weixin.qq.com/s/iYZXtqFWgqf4sWSpMCIrLA
2、https://mp.weixin.qq.com/s/2ka1cDTRyjsAGk_-ii4ngw
volatile特点
三大特性:
- 保证可见性
- 不保证原子性
- 禁止指令重排
- 适合在一个线程写,其它线程读的情况
锁 | 优势 | 劣势 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要而外的消耗,和执行非同步方法比,仅存在纳秒级的差距 | 如果线程间存在锁竞争,会带来额外锁撤销的消耗 | 适用只有一个线程访问同步块的场景 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 始终得不到锁的线程会适用自旋,从而消耗cpu,到达一定的自旋数,讲膨胀为重量级锁 | 追求响应时间,同步块执行速度非常快 |
重量级锁 | 线程不使用自旋,不适用cupu | 线程阻塞,响应速度缓慢 | 追求吞吐量,同步块执行速度较长 |
java总共有15种锁
https://blog.csdn.net/xiaoxiaole0313/article/details/103900585
AQS原理讲解:
https://www.cnblogs.com/zhangweicheng/p/12000213.html
CLH队列锁
https://blog.csdn.net/aesop_wubo/article/details/7533186
AtomicReference 原子性引用
https://www.jianshu.com/p/5521ae322743
深入理解sun.misc.Unsafe原理
https://blog.csdn.net/zyzzxycj/article/details/89877863
CAS (Compare-And-Swap) 对比和替换
这个类的提供了一些绕开JVM的更底层功能,基于它的实现可以提高效率。但是,它是一把双刃剑:正如它的名字所预示的那样,它是Unsafe的,它所分配的内存需要手动free(不被GC回收)
内部类Node中关键字段:
thread:争夺资源的线程,存放在节点当中。
prev:同步队列中的上一个节点。
next:同步队列的下一个节点。
nextWaiter:下一个等待节点,用来实现等待队列。
waitStatus :节点状态
waitStatus :节点状态
取值为-3~1(整数)。
当状态:1
时表示没用了,其他状态表示是有用的。0
时初始状态或者不代表任何意义时的取值。
- SIGNAL(-1):这个状态一般由下一个节点来设置,代表的意思是当前节点在释放了资源后将后续节点的线程唤醒。(大白话就是后续节点拜托前方的大哥东西用完了叫他,他先去睡会儿)
- CONDITION(-2):表示节点处于等待队列中,等待队列中的节点不会参与资源竞争,必须从等待队列出来后重新加入同步队列才能参与竞争。
- PROPAGATE(-3):在共享模式的时候用到。共享模式下,不仅只是唤醒下个节点,还可能唤醒下下个节点(根据当前剩余资源state的值能否满足最近节点的需求决定)。
- CANCELLED(1):表示该节点没用了,可能是等太久了,也可能是其他原因,总之就是废了,处于该状态的节点不会再改变,所以AQS中经常会判断节点状态是否大于0来检查节点是否还有用。
Unsafe类中的核心方法
//重新分配内存
public native long reallocateMemory(long address, long bytes);
//分配内存
public native long allocateMemory(long bytes);
//释放内存
public native void freeMemory(long address);
//在给定的内存块中设置值
public native void setMemory(Object o, long offset, long bytes, byte value);
//从一个内存块拷贝到另一个内存块
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//获取值,不管java的访问限制,其他有类似的getInt,getDouble,getLong,getChar等等
public native Object getObject(Object o, long offset);
//设置值,不管java的访问限制,其他有类似的putInt,putDouble,putLong,putChar等等
public native void putObject(Object o, long offset);
//从一个给定的内存地址获取本地指针,如果不是allocateMemory方法的,结果将不确定
public native long getAddress(long address);
//存储一个本地指针到一个给定的内存地址,如果地址不是allocateMemory方法的,结果将不确定
public native void putAddress(long address, long x);
//该方法返回给定field的内存地址偏移量,这个值对于给定的filed是唯一的且是固定不变的
public native long staticFieldOffset(Field f);
//报告一个给定的字段的位置,不管这个字段是private,public还是保护类型,和staticFieldBase结合使用
public native long objectFieldOffset(Field f);
//获取一个给定字段的位置
public native Object staticFieldBase(Field f);
//确保给定class被初始化,这往往需要结合基类的静态域(field)
public native void ensureClassInitialized(Class c);
//可以获取数组第一个元素的偏移地址
public native int arrayBaseOffset(Class arrayClass);
//可以获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用, 可以定位数组中每个元素在内存中的位置
public native int arrayIndexScale(Class arrayClass);
//获取本机内存的页数,这个值永远都是2的幂次方
public native int pageSize();
//告诉虚拟机定义了一个没有安全检查的类,默认情况下这个类加载器和保护域来着调用者类
public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
//定义一个类,但是不让它知道类加载器和系统字典
public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);
//锁定对象,必须是没有被锁的
public native void monitorEnter(Object o);
//解锁对象
public native void monitorExit(Object o);
//试图锁定对象,返回true或false是否锁定成功,如果锁定,必须用monitorExit解锁
public native boolean tryMonitorEnter(Object o);
//引发异常,没有通知
public native void throwException(Throwable ee);
//CAS,如果对象偏移量上的值=期待值,更新为x,返回true.否则false.类似的有compareAndSwapInt,compareAndSwapLong,compareAndSwapBoolean,compareAndSwapChar等等。
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
// 该方法获取对象中offset偏移地址对应的整型field的值,支持volatile load语义。类似的方法有getIntVolatile,getBooleanVolatile等等
public native Object getObjectVolatile(Object o, long offset);
//线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。
public native void park(boolean isAbsolute, long time);
//终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,也正是使用这两个方法
public native void unpark(Object thread);
//获取系统在不同时间系统的负载情况
public native int getLoadAverage(double[] loadavg, int nelems);
//创建一个类的实例,不需要调用它的构造函数、初使化代码、各种JVM安全检查以及其它的一些底层的东西。即使构造函数是私有,我们也可以通过这个方法创建它的实例,对于单例模式,简直是噩梦。
public native Object allocateInstance(Class cls) throws InstantiationException;
Synchronized用法的举例说明(是否互斥的核心:锁的对象是否是一样!):
public class LockSynchronized {
// 是否互斥,核心:锁的对象是否是一样!!!!!
public static void main(String[] args) {
LockSynchronized lockSynchronized = new LockSynchronized();
lockSynchronized.test0();
/* 执行test0() 打印
--------func2--------start1: 1712834689317
--------func1--------start1: 1712834689317
--------func2--------start2: 1712834689317
--------func1--------start2: 1712834689317
--------func1--------stop2: 1712834689317
--------func1--------stop1: 1712834689317
--------func2--------stop2:1712834689317
--------func2--------stop1:1712834689317
*/
// /*******************对象锁*******************/
// lockSynchronized.test1();
// lockSynchronized.test11();
// lockSynchronized.test2();
// /*****************对象锁*******************/
//
// /***************类锁*******************/
// lockSynchronized.test3();
// lockSynchronized.test33();
// /***************类锁*******************/
//
// /***************类锁+对象锁*******************/
// lockSynchronized.test4();
// lockSynchronized.test44();
// /***************类锁+对象锁*******************/
}
private void test0(){
LockTest0 lockTest0 = new LockTest0();
new Thread(lockTest0::func2).start();
new Thread(lockTest0::func1).start();
}
/**
* 对象锁判断是否互斥:锁的对象是否一样
* 锁指向this, 且this均为lockTest1,因此func1()与func2()互斥
*/
private void test1() {
LockTest1 lockTest1 = new LockTest1();
// LockTest1 lockTest2 = new LockTest1();
new Thread(lockTest1::func1).start();
new Thread(lockTest1::func2).start();
}
/**
* 对象锁判断是否互斥:锁的对象是否一样
* 锁指向this, 但this的对象分别为lockTest1和lockTest2,因此func1()与func2()非互斥
*/
private void test11() {
LockTest1 lockTest1 = new LockTest1();
LockTest1 lockTest11 = new LockTest1();
new Thread(lockTest1::func1).start();
new Thread(lockTest11::func2).start();
}
/**
* 对象锁判断是否互斥:锁的对象是否一样
* 锁的对象不相同,分别为this、object, 锁都不一样,因此func1()与func2()非互斥
*/
private void test2() {
LockTest2 lockTest2 = new LockTest2();
// LockTest2 lockTest22 = new LockTest2();
new Thread(lockTest2::func1).start();
new Thread(lockTest2::func2).start();
}
/**
* 类锁判断是否互斥:锁是否同一个类
* 类锁:锁为类LockTest3, ,不管对象是否相同(对象均是lockTest3),因此func1()与func2()为互斥
*/
private void test3() {
LockTest3 lockTest3 = new LockTest3();
// LockTest3 lockTest22 = new LockTest3();
new Thread(lockTest3::func1).start();
new Thread(lockTest3::func2).start();
}
/**
* 类锁判断是否互斥:锁是否同一个类
* 类锁:锁为类LockTest3, ,不管对象是否相同(对象分别为lockTest3,lockTest33),因此func1()与func2()为互斥
*/
private void test33() {
LockTest3 lockTest3 = new LockTest3();
LockTest3 lockTest33 = new LockTest3();
new Thread(lockTest3::func1).start();
new Thread(lockTest33::func2).start();
}
/**
* 核心:锁的对象是否一致
* 锁为类LockTest4与object, 两个方法中锁的对象都不一样,因此func1()与func2()为非互斥
*/
private void test4() {
LockTest4 lockTest4 = new LockTest4();
// LockTest4 lockTest44 = new LockTest4();
new Thread(lockTest4::func1).start();
new Thread(lockTest4::func2).start();
}
/**
* 核心:锁的对象是否一致
* 锁为类LockTest4与object, 两个方法中锁的对象都不一样,因此func1()与func2()为非互斥
*/
private void test44() {
LockTest4 lockTest4 = new LockTest4();
LockTest4 lockTest44 = new LockTest4();
new Thread(lockTest4::func1).start();
new Thread(lockTest44::func2).start();
}
}
/**
* 对象锁1
*/
class LockTest1 {
public void func1() {
synchronized (this) { // monitor: this
System.out.println("--------func11--------" + System.currentTimeMillis());
}
}
public void func2() {
synchronized (this) { // monitor: this
System.out.println("--------func2--------" + System.currentTimeMillis());
}
}
}
/**
* 对象锁2
*/
class LockTest2 {
private final Object object = new Object();
public void func1() {
synchronized (this) { // monitor: this
System.out.println("--------func11--------" + System.currentTimeMillis());
}
}
public void func2() {
synchronized (object) { // monitor: object
System.out.println("--------func2--------" + System.currentTimeMillis());
}
}
}
/**
* 类锁1
*/
class LockTest3 {
public void func1() {
synchronized (LockTest3.class) { // monitor: class
System.out.println("--------func11--------" + System.currentTimeMillis());
}
}
public void func2() {
synchronized (LockTest3.class) { // monitor: class
System.out.println("--------func2--------" + System.currentTimeMillis());
}
}
}
/**
* 混合:类锁+对象锁
*/
class LockTest4 {
private final Object object = new Object();
public void func1() {
synchronized (LockTest4.class) { // monitor: class
System.out.println("--------func11--------" + System.currentTimeMillis());
}
}
public void func2() {
synchronized (object) { // monitor: object
System.out.println("--------func2--------" + System.currentTimeMillis());
}
}
}
/**
* 参考HandlerThread源码中的对象锁
* sleep与wait区别,sleep:阻塞且不释放资源。wait阻塞但释放资源(释放锁与cpu等)
* notifyAll: 刷新所有wait的锁,不管notifyAll放在synchronized 代码块的任意地方,都会执行完代码块里的全部代码之后再去刷新wait
* notify: 刷新单个wait
*/
class LockTest0 {
boolean isOK;
public void func1() {
System.out.println("--------func1--------start1: " + System.currentTimeMillis());
synchronized (this) {
try {
System.out.println("--------func1--------start2: " + System.currentTimeMillis());
// Thread.sleep(2000);
isOK = true;
notifyAll();
System.out.println("--------func1--------stop2: " + System.currentTimeMillis());
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("--------func1--------stop1: " + System.currentTimeMillis());
}
}
public void func2() {
System.out.println("--------func2--------start1: " + System.currentTimeMillis());
synchronized (this) {
while (!isOK) {
try {
System.out.println("--------func2--------start2: " + System.currentTimeMillis());
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("--------func2--------stop2:" + System.currentTimeMillis());
}
}
System.out.println("--------func2--------stop1:" + System.currentTimeMillis());
}
}