您当前的位置: 首页 >  Java

止步前行

暂无认证

  • 0浏览

    0关注

    247博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Java并发中各种锁介绍

止步前行 发布时间:2019-03-10 22:02:27 ,浏览量:0

一、引言

在学习并发的过程中,会遇到很多种锁,如果理解不好,或有一些困惑。下面就对这些锁做一下总结,就当做一个笔记吧。

锁有如下种类:

(1)、公平锁 / 非公平锁
(2)、可重入锁 / 不可重入锁
(3)、独享锁 / 共享锁
(4)、互斥锁 / 读写锁
(5)、乐观锁 / 悲观锁
(6)、分段锁
(7)、偏向锁 / 轻量级锁 / 重量级锁
(8)、自旋锁

上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面的内容是对每个锁的名词进行一定的解释。

二、锁的介绍 1、公平锁 / 非公平锁

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

非公平锁:非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

对于 ReentrantLock 来说,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。 对于 Synchronized 而言,也是一种非公平锁。由于其并不像 ReentrantLock 是通过 AQS 的来实现线程调度,所以并没有任何办法使其变成公平锁。

2、可重入锁 / 不可重入锁

可重入锁: 广义上的可重入锁 指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock 和 synchronized 都是可重入锁

synchronized void setA() throws Exception{
   Thread.sleep(1000);
   setB();
}
synchronized void setB() throws Exception{
   Thread.sleep(1000);
}

上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

不可重入锁:不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。一个经典的讲解,使用自旋锁来模拟一个不可重入锁,代码如下:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

   private AtomicReference owner = new AtomicReference();

   public void lock() {
       Thread current = Thread.currentThread();
       //这句是很经典的“自旋”语法,AtomicInteger中也有
       for (;;) {
           if (!owner.compareAndSet(null, current)) {
               return;
           }
       }
   }

   public void unlock() {
       Thread current = Thread.currentThread();
       owner.compareAndSet(current, null);
   }
}

上面代码也比较简单,使用原子引用来存放线程,同一线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁,这个锁就不是可重入的,而实际上同一个线程不必每次都去释放锁再来获取锁,这样的调度切换是很耗资源的。

把它变成一个可重入锁:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

   private AtomicReference owner = new AtomicReference();
   private int state = 0;

   public void lock() {
       Thread current = Thread.currentThread();
       if (current == owner.get()) {
           state++;
           return;
       }
       //这句是很经典的“自旋”式语法,AtomicInteger中也有
       for (;;) {
           if (!owner.compareAndSet(null, current)) {
               return;
           }
       }
   }

   public void unlock() {
       Thread current = Thread.currentThread();
       if (current == owner.get()) {
           if (state != 0) {
               state--;
           } else {
               owner.compareAndSet(current, null);
           }
       }
   }
}

在执行每次操作之前,判断当前锁持有者是否是当前对象,采用 state 计数,不用每次去释放锁。

ReentrantLock 中可重入锁实现

这里看非公平锁的锁获取方法:

final boolean nonfairTryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) {
       if (compareAndSetState(0, acquires)) {
           setExclusiveOwnerThread(current);
           return true;
       }
   } else if (current == getExclusiveOwnerThread()) {//就是这里
       int nextc = c + acquires;
       if (nextc  0) {// 如果大于0,表示当前线程多次获取了该锁,释放锁通过count减一来模拟
               count--;
           } else {// 如果count==0,可以将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。
               cas.compareAndSet(cur, null);
           }
       }
   }
}

自旋锁与互斥锁

(1)、自旋锁与互斥锁都是为了实现保护资源共享的机制。
(2)、无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。
(3)、获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。

自旋锁总结:

(1)、自旋锁:线程获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。

(2)、自旋锁等待期间,线程的状态不会改变,线程一直是用户态并且是活动的(active)。

(3)、自旋锁如果持有锁的时间太长,则会导致其它等待获取锁的线程耗尽CPU。

(4)、自旋锁本身无法保证公平性,同时也无法保证可重入性。

(5)、基于自旋锁,可以实现具备公平性和可重入性质的锁。
关注
打赏
1657848381
查看更多评论
立即登录/注册

微信扫码登录

0.0429s