Java锁

参考:https://segmentfault.com/a/1190000008471362
https://www.cnblogs.com/qifengshi/p/6831055.html

锁的分类

不可重入锁/可重入锁

不可重入锁同一个线程在外层方法获取锁的时候,在进入内层方法会阻塞,直到外层方法释放锁。

public class Lock{
    private boolean isLocked = false;
    public synchronized void lock() throws InterruptedException{
        while(isLocked){    
            wait();
        }
        isLocked = true;
    }
    public synchronized void unlock(){
        isLocked = false;
        notify();
    }
}

使用该锁

public class Count{
    Lock lock = new Lock();
    public void print(){
        lock.lock();
        doAdd();
        lock.unlock();
    }
    public void doAdd(){
        lock.lock();//按照常规逻辑,既然该线程已经获取了锁,则该线程已经用了该资源,不需要再申请该资源
        //do something
        lock.unlock();
    }
}

可重入锁:在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
ReentrantLock,ReentrantReadWriteLock,synchronized也可以实现可重入。

synchronized void setA() throws Exception{   
    Thread.sleep(1000);
    setB();   // 因为获取了setA()的锁(即获取了方法外层的锁)、此时调用setB()将会自动获取setB()的锁,如果不自动获取的话方法B将不会执行           
}
 
synchronized void setB() throws Exception{
    Thread.sleep(1000);
 }

公平锁/非公平锁

公平锁:指多个线程按照申请锁的顺序来获取锁。(队列)
非公平锁:指多个线程获取锁的顺序不是按照申请锁的顺序。可能导致优先级反转或者饥饿现象。
比较
非公平锁比公平锁性能高5-10倍,因为公平锁需要在多核情况下维护一个队列,如果当前线程不是队列的第一个无法获取锁,增加了线程切换次数。
应用
在Java中ReentrantLock,ReentrantReadWriteLock和Semaphore可设置成公平锁。

独享锁/共享锁

独享锁指该锁一次只能被一个线程所持有。
共享锁指该锁可被多个线程持有。

互斥锁/读写锁

互斥锁是独享锁的实现,读和写均要加锁。
读写锁中读锁为共享锁,写锁是独享的,中(ReadWriteLock,ReentrantReadWriteLock),写加锁,读不加锁。

乐观锁/悲观锁

乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断尝试更新数据。乐观的认为,不加锁的并发操作是没有事情的。
应用:悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。
悲观锁在Java中的使用,就是利用各种锁。
乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。
重量级锁是悲观锁的一种,自旋锁、轻量级锁与偏向锁属于乐观锁

分段锁

对于ConcurrentHashMap的并发就是通过分段锁的形式实现。ConcurrentHashMap的分段锁称为Segment(extends ReentranLock)。
当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

偏向锁/轻量级锁/重量级锁

指锁的状态,并且是针对Synchronized。三种锁的状态是通过对象监视器(monitor)在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

自旋锁

自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

具体的锁

Synchronized是基于JVM来保证数据同步的,而Lock则是在硬件层面,通过Unsafe类中native方法根据平台不同,依赖特殊的CPU指令实现数据同步的。
Synchronized:非公平、悲观、独享、互斥、可重入的重量级锁。
ReentrantLock:默认非公平可实现公平、悲观、独享、互斥、可重入的重量级锁。
ReentrantReadWriteLoack:默认非公平可实现公平、悲观、写独享、读共享、可重入的重量级锁。

Synchronized

可以把非NULL的对象当作锁
1、作用于方法时,锁住的是对象实例(this)。
2、作用于静态方法时,锁住的是Class实例。
3、synchronized作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。

Lock锁简介

与synchronized不同的是,Lock锁是纯Java实现的,与底层的JVM无关。在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock、ReadWriteLock(实现类ReentrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类(简称AQS)

Lock锁实现

获取锁:首先判断当前状态是否允许获取锁,如果是就获取锁,否则就阻塞操作或者获取失败,也就是说如果是独占锁就可能阻塞,如果是共享锁就可能失败。另外如果是阻塞,那么线程就需要进入阻塞队列。当状态位允许获取锁时就修改状态,就从队列中取出线程。

Lock与Synchronized区别:
1、synchronized:当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待;而Lock可以中断或只等待一定的时间。
2、Lock可实现读写锁
3、Lock需要手动释放锁
4、实现方式:JVM实现;通过代码实现

AQS

AQS是我们后面将要提到的CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基础,因此AQS也是Lock和Excutor实现的基础。它的基本思想就是一个同步器,支持获取锁和释放锁两个操作。通过组合方式,将实现了的AQS子类,作为锁的成员变量。

AQS成员变量

AQS的实现依赖内部的同步队列(FIFO双向队列),如果当前线程获取同步状态失败,AQS会将该线程以及等待状态等信息构造成一个Node,将其加入同步队列的尾部,同时阻塞当前线程,当同步状态释放时,唤醒队列的头节点。

	private transient volatile Node head;
    private transient volatile Node tail;
    private volatile int state;

获取锁:
假设线程A要获取同步状态(这里想象成锁,方便理解),初始状态下state=0,所以线程A可以顺利获取锁,A获取锁后将state置为1。在A没有释放锁期间,线程B也来获取锁,此时因为state=1,表示锁被占用,所以将B的线程信息和等待状态等信息构成出一个Node节点对象,放入同步队列,head和tail分别指向队列的头部和尾部(此时队列中有一个空的Node节点作为头点,head指向这个空节点,空Node的后继节点是B对应的Node节点,tail指向它),同时阻塞线程B(这里的阻塞使用的是LockSupport.park()方法)。后续如果再有线程要获取锁,都会加入队列尾部并阻塞。
释放锁:
当线程A释放锁时,即将state置为0,此时A会唤醒头节点的后继节点(所谓唤醒,其实是调用LockSupport.unpark(B)方法),即B线程从LockSupport.park()方法返回,此时B发现state已经为0,所以B线程可以顺利获取锁,B获取锁后B的Node节点随之出队。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值