锁
在 Java 多线程环境中,锁是确保共享资源线程安全的重要手段。
参考文章:
基本介绍
什么是锁
在 Java 多线程环境中,锁是确保共享资源线程安全的重要手段。 当线程要操作共享资源时,需先获取对应的锁,以此保证在操作过程中,该资源不会被其他线程访问。待操作结束后,线程释放锁,使其他线程有机会获取并操作该资源。
为什么需要锁
锁可以确保多个线程之间对共享资源的访问是互斥的,也就是同一时刻只有一个线程能够访问被保护的共享资源,从而避免并发访问带来的数据不一致性和竞态条件等问题,是解决线程安全问题常用手段之一。
什么是死锁及如何防止死锁
当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。防止死锁有如下方式:
- 尽量使用 
tryLock(long timeout, TimeUnit unit)的方法 (ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。 - 尽量使用 Java. util. concurrent 并发类代替自己手写锁。
 - 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
 - 尽量减少同步的代码块。
 
Synchronized锁
synchronized 由一对 monitorenter/monitorexit 指令实现,monitor 对象是同步的基本实现单元。
在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。可以作用于实例方法、静态方法、代码块,能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。synchronized锁基于对象头、CAS、Monitor对象,
在 Java 6 的时候,Java 虚拟机提供了三种 monitor 实现:偏向锁(Biased Locking)、轻量级锁和重量级锁,改进了性能。
作用于三个位置:
作用在静态方法上,则锁是当前类的Class对象。
作用在普通方法上,则锁是当前的实例(this)。
作用在代码块上,则需要在关键字后面的小括号里,显式指定锁对象,例如this、Xxx.class。
对象头存储锁信息: synchronized的底层是采用对象头的Mark Word来存储锁信息的。Hotspot 虚拟机(JVM默认虚拟机)中每个对象都有一个对象头(Object Header),包含Mark Word(标记字段) 和 Class Pointer(类型指针)。
Mark Word(标记字段):存储哈希码、GC分代年龄、锁信息、GC标记(标志位,标记可达对象或垃圾对象)等。锁信息包括:
锁标志位:64位的二进制数,通过末尾能判断锁状态。01未锁定、01可偏向、00轻量级锁、10重量级锁、11垃圾回收标记
偏向锁线程ID、时间戳等;
轻量级锁的指针:指向锁记录的指针
重量级锁的指针:指向Monitor锁的指针
类型指针:指向它的类元数据的指针,用于找到对象所在的类
不考虑共享资源是类变量等特殊情况的话,有共享资源的多个线程通常都属于同一个对象。
Monitor对象:每个 Java 对象都可以关联一个 Monitor 对象,也称为监视器锁或Monitor锁。Monitor锁用于控制线程对共享资源的访问,开发人员不能直接访问Monitor对象。当一个线程获取了Monitor的锁后,其他试图获取该锁的线程就会被阻塞,直到当前线程释放锁为止。
当一个线程执行synchronized方法或代码块并升级成重量级锁时,当前对象会关联一个Monitor对象,线程须获得该对象的Monitor锁才能执行。Monitor有Owner、EntryList、WaitSet三个字段,分别表示Monitor的持有者线程(获得锁的线程)、阻塞队列、和等待队列。
线程通信:synchronized通过Monitor对象,利用Object的wait,notify,notifyAll等方法来实现线程通信。
锁升级:JDK6之前synchronized只有无锁和重量级锁两个状态,JDK6引入偏向锁、轻量级锁两个状态,锁可以根据竞争程度从无锁状态慢慢升级到重量级锁。当竞争小的时候,只需以较小的代价加锁,直到竞争加剧,才使用重量级锁,从而减小了加锁带来的开销。
- 锁升级顺序:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。
 - 无锁:没有线程访问同步代码块时。没有对资源进行锁定,所有的线程都能访问并不断修改同一个资源,但同时只有一个线程能修改成功,失败线程会不断重试。
 - 偏向锁:当有一个线程访问同步代码块时升级为偏向锁。一段同步代码块一直被一个线程所访问,那么该线程id会CAS写入对象头,下次再访问同步代码块时对象头检查到该线程id,就不用加锁解锁了,降低获取锁的代价。
 - 轻量级锁(自旋锁):有锁竞争时升级为轻量级锁。其他线程会通过自旋的形式尝试通过CAS将对象头中Mark Word替换为指向线程栈帧里锁记录的指针,从而获得锁;同时线程锁记录里存放Mark Word信息。竞争的线程不会阻塞但会一直自旋,消耗CPU性能,但速度快。
 - 重量级锁:锁膨胀(自旋失败10次)时升级为重量级锁。Mark Word中存储的是指向Monitor锁的指针,对象Mark Word信息也会保存在Monitor锁里,当一个线程获取了Monitor锁后,竞争线程会被阻塞,不再自旋,不消耗CPU,速度慢。
 
多线程中 synchronized 锁升级的原理
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
synchronized 和 volatile 的区别
volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
volatile 仅能实现变量的修改可见性但不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
synchronized 和 ReentrantLock 的区别
synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但在 Java 6 中对 synchronized 进行了非常多的改进。主要区别如下:
- ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
 - ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
 - ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰类、方法、代码块等。
 
atomic 的原理
atomic 主要利用 CAS (Compare And Wap) 、 volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
Lock锁
Lock提供比同步方法和代码块更广泛的锁定操作。
- lock():获取锁。如果锁不可用,则当前线程将被禁用,直到获取锁为止。
 - tryLock():尝试获取锁,如果锁可用,则获取并立即返回 true;如果锁不可用,则立即返回 false,不会等待。
 - tryLock(long time, TimeUnit unit):尝试在指定的时间内获取锁。如果锁可用,则获取并立即返回 true;如果在指定时间内锁不可用,则等待直到超时,然后返回 false。
 - unlock():释放锁。
 - newCondition():返回一个绑定到此 Lock 实例的新 Condition 实例,可以用于线程之间的协调等待。
 
1  | import java.util.concurrent.locks.Lock;  | 
synchronized 和 Lock 的区别
Lock和synchronized有以下几点不同:
- 作用范围。synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
 - 接口和关键字。Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现。
 - 死锁问题。synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。
 - 让等待锁的线程响应中断。Lock可以可以通过lockInterruptibly()获取锁的方法让等待锁的线程响应中断。而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
 - 得知是否成功获取锁。通过Lock可以通过tryLock()知道有没有成功获取锁,而synchronized却无法办到。
 - 性能对比。两者性能差不多。JDK6之前synchronized没有锁升级,线程竞争非常激烈时Lock的性能要远远优于synchronized;而JDK6引入锁升级后,线程竞争激烈时候两者性能也相差无几。
 
lock锁中断线程:若有线程已拿到锁,其他线程使用lock()获取锁时会阻塞,使用lockInterruptibly()获取锁时会直接中断抛出InterruptedException异常。
lock锁编码习惯:加锁代码要放到try外面。如果放在try里面的话,加锁失败抛异常或者加锁前的代码抛异常后,执行finally里的解锁代码,而其实加锁都没成功,最终解锁就也不合适了。
1  | lock.lock(); // 加锁  | 
分布式锁:SETNX、Redisson。Redisson基于Redis协议,可以实现可重入锁、公平锁、读写锁、信号量、闭锁(计数器),支持看门狗自动续期。
反射
基本介绍
反射:在程序运行期间动态地获取类的信息并对类进行操作的机制。
通过反射机制可以实现:
获取类或对象的Class对象:程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的所有方法和属性(包括私有,私有需要给该字段调用
setAccessible(true)方法开启私有权限)。注意类的class对象是运行时生成的,类的class字节码文件是编译时生成的。创建实例:程序运行时,可以利用反射先创建类的Class对象再创建该类的实例,并访问该实例的成员;Xxx.class.newInstance() ;例如在Spring容器类源码里,Bean实例化就是通过Bean类的Class对象。Bean类的Class对象是从BeanDefinition对象的type成员变量取的。BeanDefinition对象存储一些Bean的类型、名称、作用域等声明信息。
生成动态代理类或对象:程序运行时,可以通过反射机制生成一个类的动态代理类或动态代理对象。例如JDK中Proxy类的newProxyInstance静态方法,可以通过它创建基于接口的动态代理对象。
类的字节码文件和Class对象的区别:
- 类的class字节码文件是编译时生成的,类的class对象是运行时生成的。
 - 类的字节码文件是存储在电脑硬盘中的文件,如Test.class;类的Class对象是存放在内存中的数据,可快速获取其中的信息;
 - 两者都存储类的各种信息;
 
获取类Class对象的JVM底层:如果该类没有被加载过,会首先通过JVM实现类的加载过程,即加载、链接(验证、准备、解析)、初始化,加载阶段会生成类的Class对象。
获取类Class对象的方法:dog.getClass();,Dog.class;,Class.forName("package1.Dog");
特点:
- 访问私有成员:构造方法、成员变量、方法对象取消访问检查可以访问私有成员;public void setAccessible(boolean flag):值为true,取消访问检查
 - 越过泛型检查:反射可以越过泛型检查,例如在ArrayList
中添加字符串  
反射的优缺点:
- 优点:
- 运行时获取属性:运行期间能够动态的获取类,提高代码的灵活性。
 - 访问私有成员:构造方法、成员变量、方法对象取消访问检查可以访问私有成员;public void setAccessible(boolean flag):值为true,取消访问检查
 - 越过泛型检查:反射可以越过泛型检查,例如在ArrayList
中添加字符串  
 - 缺点:性能差。性能比直接的Java代码要差很多。
 
应用场景:
- JDBC加载数据库的驱动:使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序;
 - Bean的生命周期:
- 实例化xml解析出的类:多数框架都支持注解或XML配置来定义应用程序中的类,从xml配置中解析出来的类是字符串,需要利用反射机制实例化;如Spring通过
<bean id="xx" class="类全限定名">和<property name="按名称注入" ref="被注入Bean的id">定义bean,然后通过Class.forName("xxx.Xxx")获取类的class对象,然后创建实例。 - 注解容器类加载Bean、实例化Bean:Bean的生命周期中,注解容器类的构造方法会遍历
@ComponentScan("扫描路径")下的.class文件,通过类加载器.load("类名")方式获得类的class对象,存入beanDefinitionMap。然后遍历beanDefinitionMap,通过class对象实例化等。 
 - 实例化xml解析出的类:多数框架都支持注解或XML配置来定义应用程序中的类,从xml配置中解析出来的类是字符串,需要利用反射机制实例化;如Spring通过
 - AOP创建动态代理对象:面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理对象,这必须由反射机制来实现。
 
验证反射可以绕过泛型检查:
基于反射,我们可以给ArrayList<Integer>对象中,加入字符串 
1  | public class Test {  | 
反射获取Class对象
基本介绍
Class类的对象:程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的所有方法和属性(包括私有,私有需要给该字段调用setAccessible(true)方法开启私有权限)。
注意类的class对象是运行时生成的,类的class字节码文件是编译时生成的。
获取类Class对象:
- 对象.getClass():Object是一切类的根类,Object类有个getClass()方法可以获取类的Class对象。例如dog.getClass();
 - 类名.class(推荐):例如Dog.class;
 - Class.forName(“类名”):例如Class.forName(“package1.Dog”);
 
Class对象的常用方法:
- 获取类的信息:
- String getName():返回类的全限定名。全限定名包含包名和类名,用于唯一标识类或接口。例如package1.Dog、java.lang.String、java.util.Map$Entry
 - String getSimpleName():返回类的简单名。例如Dog
 - tring getCanonicalName():返回类的规范名。规范名是类的规范字符串形式,常用于打印和日志记录。例如package1.Dog、java.lang.String、java.util.Map.Entry
 - Package getPackage():返回此类所属的包。
 - ClassLoader getClassLoader():返回该类的类加载器。
 - Class<? super T> getSuperclass():返回表示类的超类的 Class 对象。
 - Class<?>[] getInterfaces():返回类实现的所有接口。
 - boolean isInterface():判断是否是接口。
 - boolean isAnnotation():判断是否是注解类型。
 - boolean isEnum():判断是否是枚举类型。
 - Annotation[] getAnnotations():返回此元素上存在的所有注解。
 - Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解。
 - T getAnnotation(Class
annotationClass):返回指定类型的注解,如果该注解存在于此元素上,否则返回 null。例如Spring源码中,ApplicaitonContext构造器判断一个类是不是Bean,是通过这个方法判断类有没有@Comonent等注解,从而判断它是不是Bean。  
 - 获取成员:
- Field[] getFields():返回类的所有公共字段,包括从父类继承的字段。
 - Field[] getDeclaredFields():返回类声明的所有字段,不包括继承的字段。
 - Method[] getMethods():返回类的所有公共方法,包括从父类继承的方法。
 - Method[] getDeclaredMethods():返回类声明的所有方法,不包括继承的方法。
 - Constructor<?>[] getConstructors():返回类的所有公共构造方法。
 - Constructor<?>[] getDeclaredConstructors():返回类声明的所有构造方法。
 
 - 其他方法:
- T newInstance():创建此 Class 对象所表示的类的一个新实例(使用默认构造方法)。
 
 
Spring源码:Bean初始化时判断类是否Bean、判断属性是否需要填充都用到了反射
1  | // 狗类  | 
全限定名和规范名
全限定名和规范名:
外部类的全限定名和规范名是一样的,都是“xxx.类名”。区别主要在内部类,内部类的全限定名是“xxx.外部类名$内部类名”,规范名是“xxx.外部类名.内部类名”。
- 简单名:只包含类名。例如Dog、String、Entry
 - 全限定名:包含包名和类名,用于唯一标识类或接口,通过全限定名能找到唯一一个类。例如package1.Dog、java.lang.String、java.util.Map$Entry
 - 规范名:类的规范字符串形式,常用于打印和日志记录。例如package1.Dog、java.lang.String、java.util.Map.Entry
 
1  | public static void main(String[] args) throws Exception {  | 
反射获取成员
反射获取构造方法
Class对象获取构造器:
- getConstructor(Class<?>… parameterTypes):获取指定参数类型的公共构造方法。返回值是Constructor类。
 - getDeclaredConstructor(Class<?>… parameterTypes):获取指定参数类型的构造方法(包括私有构造方法)。
 - getConstructors():获取所有公共构造方法。
 - getDeclaredConstructors():获取所有构造方法(包括私有构造方法)。
 - **newInstance()**:创建类的新实例。
- Class类的newInstance():只能够调用无参构造函数;
 - Constructor类的newInstance():可以根据传入的参数,调用任意构造函数。
 
 
1  | // 获取所有构造器对象:  | 
反射获取字段
Class对象获取字段:
- getField(String name):返回指定名称的公共字段。返回类型是字段类Field。
 - getDeclaredField(String name):返回指定名称的字段(包括私有字段)。
 - getFields():返回所有公共字段。
 - getDeclaredFields():返回所有字段(包括私有字段)。
 
字段类Field常用方法:
获取字段信息:
- getName():返回字段的名称。
 - getType():返回字段的类型。
 - getModifiers():返回字段的修饰符。
 - getDeclaringClass():返回声明该字段的类的 Class 对象。
 
获取和设置字段值:
- get(Object obj):返回指定对象上此字段的值。
 - getBoolean(Object obj):返回指定对象上此字段的值(如果字段类型是 boolean)。
 - getByte(Object obj):返回指定对象上此字段的值(如果字段类型是 byte)。
 - getChar(Object obj):返回指定对象上此字段的值(如果字段类型是 char)。
 - getDouble(Object obj):返回指定对象上此字段的值(如果字段类型是 double)。
 - getFloat(Object obj):返回指定对象上此字段的值(如果字段类型是 float)。
 - getInt(Object obj):返回指定对象上此字段的值(如果字段类型是 int)。
 - getLong(Object obj):返回指定对象上此字段的值(如果字段类型是 long)。
 - getShort(Object obj):返回指定对象上此字段的值(如果字段类型是 short)。
 - set(Object obj, Object value):设置指定对象上此字段的值。注意私有字段默认不允许赋值,要赋值必须给私有字段setAccessible(true)。
 - setBoolean(Object obj, boolean value):设置指定对象上此字段的值(如果字段类型是 boolean)。
 - setByte(Object obj, byte value):设置指定对象上此字段的值(如果字段类型是 byte)。
 - setChar(Object obj, char value):设置指定对象上此字段的值(如果字段类型是 char)。
 - setDouble(Object obj, double value):设置指定对象上此字段的值(如果字段类型是 double)。
 - setFloat(Object obj, float value):设置指定对象上此字段的值(如果字段类型是 float)。
 - setInt(Object obj, int value):设置指定对象上此字段的值(如果字段类型是 int)。
 - setLong(Object obj, long value):设置指定对象上此字段的值(如果字段类型是 long)。
 - setShort(Object obj, short value):设置指定对象上此字段的值(如果字段类型是 short)。
 
其他方法:
- isAccessible():返回字段是否可访问。
 - setAccessible(boolean flag):设置字段的可访问性。通过这个方法可以让私有字段也可以赋值。
 - oGenericString():返回字段的描述,包括泛型信息。
 - getAnnotatedType():返回此字段的带注释的类型。
 - getAnnotations():返回字段的所有注解。
 - getAnnotation(Class
annotationClass): 返回字段的指定类型的注解,如果该注解不存在,则返回 null。例如Spring源码中依赖注入这一块,就是基于反射获取类中字段有没有@Resource、@Component等注解,有的话就是要注入Bean. - getDeclaredAnnotations():返回直接存在于此字段上的所有注解。
 
1  | // 获取成员变量对象并赋值  | 
反射获取普通方法
Class对象获取成员方法的方法:
getMethod(String name, Class<?>… parameterTypes):返回指定名称和参数类型的公共方法。返回值是方法类Method。
getDeclaredMethod(String name, Class<?>… parameterTypes):返回指定名称和参数类型的方法(包括私有方法)。
getMethods():返回所有公共方法(包括从父类继承的方法)。
getDeclaredMethods():返回所有方法(包括私有方法)。
Method类的方法:
- 获取方法信息:
- getName():返回方法的名称。
 - getReturnType():返回方法的返回类型。
 - getParameterTypes():返回方法参数类型的数组。
 - getModifiers():返回方法的修饰符。
 - getDeclaringClass():返回声明此方法的类的 Class 对象。
 
 
- 获取方法信息:
 调用方法:
- Object invoke(Object obj, Object… args):调用指定对象上此 Method 对象表示的基础方法。
 
其他方法:
- isAccessible():返回方法是否可访问。
 - setAccessible(boolean flag):设置方法的可访问性。
 - getAnnotations():返回此方法的所有注解。例如Spring源码中通过此方法判断一个类中
 - isAnnotationPresent(Class<? extends Annotation> annotationClass):判断此方法是否被指定的注解类型注释。
 - getAnnotation(Class
annotationClass):返回该方法的指定类型的注解。  - getExceptionTypes():返回此方法抛出的异常类型的数组。
 - toGenericString():返回方法的描述,包括泛型信息。
 
获取成员变量对象并调用:
1  | // 1.获取构造方法对象并实例化  | 





