定义:在 Java 中,锁是一种用于实现多线程之间同步和互斥的机制。锁是一种抽象的概念,用于控制对共享资源的访问。它确保在同一时间只有一个线程可以访问被锁保护的代码块或对象。Java 中的锁可以是显式的(如通过ReentrantLock类实现)或隐式的(如使用synchronized关键字)
产生: 多线程编程的需求: 在多线程环境下,多个线程可能同时访问共享资源。如果没有适当的同步机制,可能会导致数据不一致、竞态条件等问题。为了解决这些问题,Java 引入了锁的概念。  
     例如,一个银行账户对象可能被多个线程同时访问来进行存款和取款操作。如果没有锁来控制对账户余额的访问,可能会出现错误的结果。
     语言设计的一部分:Java 语言的设计者认识到多线程编程的复杂性和潜在问题,因此在语言中提供了内置的同步机制,如synchronized关键字和java.util.concurrent.locks包中的显式锁。这些机制使得开发人员能够更容易地实现线程安全的代码。
作用:保证线程安全:
        锁确保了对共享资源的互斥访问,防止多个线程同时修改同一数据,从而保证了数据的一致性。
        例如,在一个多线程的计数器程序中,使用锁可以确保在多个线程同时增加计数器值时,不会出现错误的结果。
     避免竞态条件:
        竞态条件是指多个线程同时访问和修改共享资源时,由于执行顺序的不确定性而导致的错误结果。锁可以避免竞态条件的发生,通过确保线程在访问共享资源时的正确顺序。
        例如,在一个生产者 - 消费者问题中,使用锁可以确保生产者在向缓冲区添加数据时,消费者不会同时从缓冲区中取出数据,从而避免了缓冲区的混乱。
     实现线程间的同步:  
        锁不仅可以用于互斥访问,还可以用于实现线程之间的同步。例如,一个线程可以等待另一个线程完成某个任务后再继续执行。
        例如,在一个多线程的任务处理系统中,一个主线程可以使用锁来等待所有子线程完成任务后再进行下一步操作。
乐观锁(Optimistic Locking)
  原理:乐观锁假设在大多数情况下,多个线程之间不会发生冲突。在读取数据时,每个线程会获得一个标识符(如版本号或时间戳)。在提交修改之前,会比较当前标识符与之前读取的标识符是否相等,如果相等则提交成功,否则说明数据已被其他线程修改,需要进行冲突处理。
  实现方式:通常使用版本号或时间戳来实现,可以在数据库中添加一个额外的字段作为标识符,并在更新操作时进行比较。
  应用场景:适用于读操作频繁而写操作较少的场景,可以减少锁的使用,提高并发性能
悲观锁(Pessimistic Locking) 
  原理:悲观锁假设在多线程环境下,对共享资源的访问会产生冲突,因此默认认为每次访问都会发生冲突,需要加锁保证独占访问。
  实现方式:可以使用synchronized关键字或Lock接口的具体实现(如ReentrantLock)来实现。
  应用场景:适用于写操作频繁的场景,因为它能够确保数据一致性和线程安全。
  synchronized:几种常见用法
     synchronized (this) {...},使用this,作为锁。
     synchronized (obj) {...},使用一个实例化对象作为锁。
     synchronized (xxx.class) {...},使用一个class作为锁。
     synchronized修饰代码块和修饰方法的区别:
     修饰代码块作用的范围是大括号{}括起来的代码;
     修饰方法作用的范围是整个函数。
     synchronized修饰代码块的时候,synchronized(this)、synchronized(obj)、synchronized(xxx.class)的相同点:
     其作用的范围是大括号{}括起来的代码;
     没有被大括号{}括起来的代码,并不会被锁住。
     synchronized修饰代码块的时候,synchronized(this)、synchronized(obj)、synchronized(xxx.class)有何不同:
     synchronized(this)指定的锁是当前类的实例化对象。同一个实例化对象,调用同步代码块的时候,会被锁住;不同实例化对象,调用同步代码块的时候,不会被锁住。
     synchronized(obj)指定的锁是指定的某个实例化对象。只要锁相同,哪怕是不同的实例化对象,调用同步代码块的时候,也会被锁住。
     synchronized(xxx.class)指定的锁是xxx.class。无论对象相不相同,只要调用这个同步代码块,就会被锁住。
     synchronized修饰方法,它的锁是什么?
     synchronized修饰方法的时候,它的锁是这个同步方法所在的对象的实例化。
     synchronized修饰方法的时候,同一个实例化对象,调用同步方法的时候,会被锁住;不同对象调用同步方法的时候,不会被锁住。
     synchronized修饰一个静态方法和修饰方法有何不同?
     synchronized修饰一个静态方法,作用范围的是静态方法所属类的所有对象。
     synchronized修饰一个方法,作用范围是这个方法所属对象的实例化。
  可重入锁(Reentrant Lock):
     原理:可重入锁是一种特殊类型的锁,允许同一个线程多次获得锁,也称为可重入性。当一个线程已经持有锁时,再次请求获取该锁是允许的,而不会导致线程被阻塞,这种机制可以避免死锁。
     实现方式:在Java中,ReentrantLock类和synchronized关键字都是可重入锁的实现。
     应用场景:适用于某个线程需要递归地调用同步方法或代码块的场景,提高代码的灵活性
  公平锁(Fair Lock)
     原理:公平锁是一种保证线程获取锁的顺序与其申请锁的顺序相同的锁机制。它会按照线程的申请顺序来分配锁资源,避免某个线程饥饿地等待锁。
     实现方式:可以使用ReentrantLock类的构造函数指定为公平锁。
     应用场景:当多个线程竞争同一个资源时,希望公平地分配锁资源,避免某个线程长时间无法获取到锁的场景。
     在公平锁中,当多个线程竞争同一个锁时,锁会按照线程等待的顺序分配给它们。这可以确保较早等待的线程优先获得锁,避免了饥饿情况的发生,即某些线程一直无法获得锁。
     需要注意的是,公平锁可能会牺牲一定的性能,因为它需要维护一个队列来管理等待的线程。因此,当性能要求较高且没有特殊需求时,可以使用非公平锁
  非公平锁:加锁时,不考虑排队情况,都尝试获取锁,性能高于公平锁5-10倍。synchronized只能非公平锁,ReentrantLock默认非公平锁

  同步锁: 属于悲观锁,同步锁在很多情况下可以理解为是一种互斥锁,但它们并不完全等同。同步锁和互斥锁都具有互斥的特性,即在同一时间只允许一个线程访问被保护的资源或代码区域,
         例如,使用synchronized关键字修饰的方法或代码块,以及使用ReentrantLock实现的锁,都能确保在同一时刻只有一个线程能够执行被锁保护的代码,这与互斥锁的行为一致。
         两者都是为了防止多个线程同时访问共享资源时出现数据不一致的问题。通过对资源的互斥访问,确保线程在修改共享数据时不会被其他线程干扰.同步锁更侧重于实现线程之间的同步操作,确保线程按照特定的顺序执行或访问共享资源。
         同步可以包括多种方式,如互斥锁只是其中一种实现同步的手段
  互斥锁:属于悲观锁,实现方式相对较为单一,主要是通过类似ReentrantLock这样的明确的互斥锁机制来实现。
  读写锁(ReadWriteLock):为了提高性能,Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制
  共享锁:也被成为读锁,允许多个线程同时获得锁,并发访问共享资源,如:ReadWriteLock。共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。
  独享锁:每次只能一个线程持有锁,ReentrantLock 就是以独占方式实现的互斥锁。独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性
  分段锁:分段锁将一个数据结构或资源划分成多个段(segment),每个段都有自己独立的锁。这样,不同的线程可以同时访问不同段上的数据,而不会相互阻塞,从而提高并发度

锁分为:
1.本地锁:synchronize、lock等,锁在当前进程内,集群部署下依旧存在问题
2.分布式锁:redis、zookeeper等实现,虽然还是锁,但是多个进程共用的锁标记,可以用Redis、Zookeeper, Mysql等都可以
分布式锁redisson  
  Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
  redission的看门狗机制:
       Redisson提供的分布式锁是支持锁自动续期的,也就是说,如果线程仍旧没有执行完,那么redisson会自动给redis中的目标key延长超时时间,这在Redisson中称之为 Watch Dog 机制。默认情况下,
       看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
     支持看门狗的分布式锁
       RLock(分布式可重入锁):RLock支持可重入性,这意味着同一个线程可以多次获取同一个锁,而每次获取都需要调用一次unlock()来释放。此外,RLock还支持锁的超时和自动续期,可以有效避免死锁的发生。 
       RReadWriteLock(分布式读写锁):RReadWriteLock允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。这可以提高并发性能,因为多个线程可以同时读取数据,而写入操作则需要独占资源。   
       RFairLock(分布式公平锁):RFairLock保证了线程按照请求锁的顺序来获取锁,避免了“饥饿”问题,即长时间等待的线程始终无法获取到锁。
  支持设置超时时间的锁是不支持看门狗机制的
       lock(long leaseTime, TimeUnit unit): 这个方法允许你指定锁的超时时间。一旦锁被持有,它将在指定的超时时间后自动释放,而不考虑看门狗机制。
       tryLock(long waitTime, long leaseTime, TimeUnit unit): 这个方法尝试获取锁,并允许你指定等待时间和锁的超时时间。如果线程在等待时间内获取到了锁,那么锁将在指定的超时时间后释放。这种情况下,看门狗机制不会启动。
       tryLock(long waitTime, TimeUnit unit, boolean autoRenew): 这个方法也允许你指定等待时间和是否自动续期。然而,即使你将autoRenew参数设置为true,如果你明确指定了锁的超时时间,看门狗机制也不会启动。因为锁的超时时间已经明确指定,
       Redisson会在超时时间到达时释放锁,而不是依赖看门狗机制来延长锁的持有时间 

锁优化技术:
      1:减少锁持有时间:只用在有线程安全要求的程序上加锁
      2: 减小锁粒度:将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。最最典型的减小锁粒度的案例就是ConcurrentHashMap
      3: 锁分离 最常见的锁分离就是读写锁 ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能。读写分离思想可以延伸,只要操作互不影响,锁就可以分离
      4:锁粗化:通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化 。锁粗化是一种优化技术,指的是在程序执行过程中,当一系列连续的对同一锁的操作被检测到时,虚拟机可能会将这些锁操作合并为一个范围更大的锁操作,以减少锁获取和释放的次数,从而提高性能
      5:锁消除:锁消除是在编译器级别的事情。在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作,多数是因为程序员编码不规范引起