ZzRG的小站 - 锁
type: channel
link: https://juziw.cn/tag/%E9%94%81/
items: synchronized锁升级
items: 关于锁的一些知识

synchronized锁升级
type: item
date: 2022-07-27T16:03:00+00:00
description: synchronized的变化①. java5以前,只有Synchronized,这个是操作系统级别的重量级操作,重量级锁,假如锁的竞争比较激烈的话,性能下降②. 在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,挂起线程和恢复线程都需要转入内核态去完成,阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,时间成本相对较高,这也是为什么早期的synchronized效率低的原因。Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁③. 为什么每一个对象都可以成为一个锁?Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。Monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,成本非常高④. Mutex LockMonitor是在jvm底层实现的,底层代码是c++。本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,状态转换需要耗费很多的处理器时间成本非常高。所以synchronized是Java语言中的一个重量级操作。⑤. Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁,需要有个逐步升级的过程,别一开始就捅到重量级锁⑥. synchronized锁:由对象头中的Mark Word根据锁标志位的不同而被复用及锁升级策略无锁偏向锁单个线程多次访问定义：①. 主要作用:当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁(偏向锁)同一个老顾客来访,直接老规矩行方便偏向锁为了解决只有在一个线程执行同步时提高性能②. 64位标记图再看(通过CAS方式修改markword中的线程ID)③. 偏向锁的理论在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁)。如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。假如不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的④. 技术实现一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的Mark Word中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识。若该线程再次访问同一个synchronized方法时,该线程只需去对象头的Mark Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入Monitor去竞争对象了。⑤. 对于如上的③、④进行细化锁对比偏向锁的操作不用直接捅到操作系统，不涉及用户到内核转换，不必要直接升级为最高级，我们以一个account对象的“对象头”为例，假如有一个线程执行到synchronized代码块的时候，JVMM使用CAS操作把线程指针ID记录到Mark Word当中，并修改标偏向标示，标示当前线程就获得该锁。锁对象变成偏向锁（通过CAS修改对象头里的锁标志位〉，字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后，线程并不会主动释放偏向锁。这时线程获得了锁，可以执行同步代码块。当该线程第二次到达同步代码块时会判断此时持有锁的线程是否还是自己（持有锁的线程ID也在对象头里)，JVM通过account对象的Mark Word判断:当前线程ID还在，说明还持有着这个对象的锁，就可以继续进入临界区工作。由于之前没有释放锁，这里也就不需要重新加锁。如果自始至终使用锁的线程风有一个，很明显偏向锁几乎没有额外开销，性能极高。结论:JVM不用和操作系统协商设置Mutex(争取内核)，它…

关于锁的一些知识
type: item
date: 2022-07-25T12:44:00+00:00
description: 乐观锁和悲观锁1、 悲观锁(synchronized关键字和Lock的实现类都是悲观锁)什么是悲观锁？认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改适合写操作多的场景,先加锁可以保证写操作时数据正确(写操作包括增删改)、显式的锁定之后再操作同步资源synchronized关键字和Lock的实现类都是悲观锁2、 乐观锁概念:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作乐观锁在Java中通过使用无锁编程来实现，最常采用的时CAS算法,Java原子类中的递增操作就通过CAS自旋实现的适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅度提升乐观锁一般有两种实现方式(采用版本号机制、CAS算法实现) //悲观锁的调用方式 public synchronized void m1(){ //加锁后的业务逻辑 } //保证多个线程使用的是同一个lock对象的前提下 ReetrantLock lock=new ReentrantLock(); public void m2(){ lock.lock(); try{ //操作同步资源 }finally{ lock.unlock(); } } //乐观锁的调用方式 //保证多个线程使用的是同一个AtomicInteger private AtomicInteger atomicIntege=new AtomicInteger(); atomicIntege.incrementAndGet();对象锁/类锁【强制】高并发时，同步调用应该去考量锁的性能损耗。能用无锁数据结构，就不要用锁;能锁区块，就不要锁整个方法体﹔能用对象锁，就不要用类锁。说明︰尽可能使加锁的代码块工作量尽可能的小，避免在锁代码块中调用RPC方法。作用于实例方法，当前实例加锁，进入同步代码前要获得当前实例的锁;作用于代码块，对括号里配置的对象加锁。作用于静态方法，当前类加锁，进去同步代码前要获得当前类对象的锁;//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { phone.sendSMS( ); // phone.hello(); // phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } }*一个对象里面如果有多个synchronized方法，某一个时刻内，只要一个线程去调归其中的一个synchronized方法了，*其它的线程都只能等待，换句话说，某一个时刻内，只能有唯一的一个线程去访问这些synchronized方法*锁的是当前对象this，被锁定后，其它的线程都不能进入到当前对象的其它的synchronized方法//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂…