java锁(转)

Java中锁分类

锁的分类

CAS

它是解决轻微冲突的多线程场景下使用锁造成性能损耗的一种机制。先是比较,如果不符合预期,则重试。它有三个操作因素:内存位置预期原值新值。如果内存位置的值与预期原值相等,则处理器将该位置值更新为新值,如果不相等,则获取当前值,然后进行不断的轮询操作直到成果达到某个阙值退出。

AQS

AbstractQueuedSynchronizer简称AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。JDK1.5中提供的java.util.concurrent包中的大多数的同步器(Synchronizer)Lock, Semaphore, Latch, Barrier等,它们都是基于java.util.concurrent.locks.AbstractQueuedSynchronizer这个类的框架实现的。

乐观锁/悲观锁

  • 乐观锁:乐观锁是一种乐观思想,认为读多写少,遇到并发的可能性低,每次拿数据时候并不会上锁,因为认为不会被别人修改。但是更新的时候会判断有没有人会更新这条数据,采取写的时候先读取版本号然后加锁,主要是和上一次版本号进行比较,如果一样则更新这条数据,如果不一样则会重复读,比较,写操作。它是基于CAS来实现的。
  • 悲观锁:悲观思想,认为写多读少,遇到并发写可能性比较高,每次读写数据都会上锁,别的线程想要读写只会被阻塞住直到拿到锁。java中的悲观锁就是SynchronizedAQS(AbstractQueuedSynchronizer)框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如RetreenLock

公平锁与非公平锁

  • 公平锁:指的是多个线程按照申请锁的顺序来获取锁。

  • 非公平锁:多个线程并不是按照申请锁的顺序获取锁,有可能后申请的比先申请的线程优先获取锁,可能造成饥饿现象,也就是线程无法访问资源而出现无法执行下去的现象。 RetreenLock它是通过构造函数来确定是不是公平锁,默认是非公平锁。非公平锁的吞吐量比公平锁大。而Synchronized是非公平锁,它没有通过AQS实现线程调度,无法成为公平锁。

  • //默认创建是非公平锁
    public ReentrantLock() {   
        sync = new NonfairSync(); 
    } 
    //通过bool值来控制是否是公平的还是不公平的,为true公平锁,为false不公平锁 
    public ReentrantLock(boolean fair) {   
        sync = fair ? new FairSync() : new NonfairSync(); 
    }
    

可重入锁

  • 也称为递归锁,线程可以重复获取一把锁,同一个线程在外层方法获取锁,进入内层方法会自动获取锁。synchronizedReentrantLock 都是可重入锁,可重入锁在一定程度上可以避免死锁。

独享锁与共享锁

  • 独享锁指的是锁一次只能被一次线程持有
  • 共享锁同时可以被多个线程持有 synchronizedReentrantLock 都是独享锁,ReadWriteLock 的读锁是共享锁,写锁是独享锁;ReentrantLock 的独享锁和共享锁也是通过 AQS来实现的。

互斥锁与读写锁

  • 其实是独享锁与共享锁具体说法;互斥锁Java中实现就是ReentrantLock,而读写锁Java实现是ReadWriteLock

分段锁

  • 实质上是一种锁的策略,并不是具体的锁。对于ConcurrentHashMap它的并发实现在JDK 11之前是都过分段锁来实现的。当需要put元素时候,并不是对hashMap整个加锁,而是通过hashCode知道在那个分段,进行分段加锁。在多线程操作中,只要put元素时候不放在同一个分段区域中,就可以进行并行插入元素,统计大小时候需要获取所有分段锁。归根结底分段锁是用来细化锁的粒度。

偏向锁

  • 从始至终只要一个线程请求一把锁。同步代码一直被一个线程访问,线程自动获取锁。 Java偏向锁是Java6引入的一项多线程优化。它会偏向第一个访问锁的线程,如果运行过程中,只有一个线程访问,没有多线程争用情况,则线程无需同步,这时候线程就会被加一个偏向锁。 但是再运行的时候,遇到其他线程占锁,则持有偏向锁的线程会被挂起,并且JVM会消除它身上的偏向锁,将锁升级为轻量级锁。

偏向锁适用场景

  • 始终只有一个线程在执行同步代码块,在它没有执行完成前,没有其他线程去执行,锁没有竞争,但是有了竞争,就会升级为轻量级锁。这时候升级的轻量级锁如果撤销的话,会触发stop the world操作。

stop the world 简介

  • Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互。

轻量级锁

  • 是由偏向锁升级而来,偏向锁时候,一个线程进入同步代码块,这时候另一个线程加入锁的争用,它就会升级为轻量级锁。 多个线程在不同时间段请求同一把锁,也就是没有竞争的情况下,Java虚拟机就会采用轻量级锁,来避免重量级锁阻塞以及重复唤醒。

轻量级锁释放

  • 当轻量级锁在释放的期间,会由轻量级锁切换到重量级锁,之前在获取锁的时候它拷贝的对象头markWord,在释放锁的时候它发现自己持有锁的时被其他线程访问,并且此线程对markword进行了修改,两者对比发现不一致就切换到重量级锁。

重量级锁

  • 它是Java中的基础锁,在这种状态下,Java虚拟机会阻塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程。Java中synchronized就是一种重量级锁。 当轻量级锁在释放的期间,会由轻量级锁切换到重量级锁,之前在获取锁的时候它拷贝的对象头mark Word,在释放锁的时候它发现自己持有锁的时被其他线程访问,并且此线程对mark word进行了修改,两者对比发现不一致就切换到重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

什么是Java对象头

  • Java对象头包括两部分信息,分别是Mark Word与元数据指针,Mark Word用于存储对象运行时的数据,比如HashCode、锁状态标志、Gc分代年龄、线程持有的锁等,而元数据指针用于指向方法区中的目标类的元数据,通过元数据可以确定对象的具体类型。

自旋锁

  • 当持有锁的线程能够在很短的时间内释放锁,而那些等待竞争锁的线程就无需做内核态与用户态之间的切换进入阻塞挂起状态,它们只需要等一等,自旋,等持有锁的线程释放锁后可以立即获取锁,减少了线程上下文切换。但是会循环造成CPU消耗增加。

解决自选锁CPU浪费

  • 如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用cpu做无用功,占着CPU却不用,并且这个时候有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cup的线程又不能获取到cpu,造成cpu的浪费。所以这种情况下我们要关闭自旋锁;

原文引自https://cloud.tencent.com/developer/article/1560084

posted @ 2020-02-17 16:41  atomFix  阅读(253)  评论(0编辑  收藏  举报