一、公平锁&非公平锁 

  公平锁:先到先得。线程A持有锁,此时线程B过来尝试获取锁,通过cas判断没有成功,B进入队列中(队列先入先出特点),C过来尝试获取锁仍然未成功也进入到队列中;一直到E过来尝试获取锁,此时A释放锁,E仍然进入队列中,由于B在队列最前边,此时B获得锁

  非公平锁:线程A持有锁,此时线程B过来尝试获取锁没有成功,B进入队列中(队列先入先出特点),C过来尝试获取锁仍然未成功也进入到队列中;一直到E过来尝试获取锁,此时A释放锁,E不用进入队列中,直接获得锁(B由于线程上下文切换是需要不少开销的,获取锁的效率低于E)

  公平锁&非公平锁优缺点及使用范围:

  非公平锁由于不存在上线文切换,执行效率更高,cpu利用率高。公平锁适用于线程执行时间大于等待线程等待时间的场景(这样会有很多线程积累),如果线程执行时间<=等待时间则不会有线程积累,采用非公平锁效率更高

  公平锁&非公平锁代码区别:

  

 //非公平锁
 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState(); //state是volitle类型,其他线程修改会对其他线程可见
            if (c == 0) { //c==0表示当前没有线程持有锁,当前线程可以尝试获取锁
              //区别重点看这里
                if (compareAndSetState(0, acquires)) { //使用CAS将当前内存位置值和期望值0比较,如果true则将当前内存位置值更新为acquires;这里采用CAS判断的原因是防止其他线程获取到锁
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { //这是代表当前线程本就持有这个锁,再来尝试获取锁的结果就是获取锁的次数+1,这表明这个锁是可重入锁
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

  //公平锁
  protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {  
              //hasQueuedPredecessors这个方法就是最大区别所在
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
公平锁的实现只多了一个hasQueuePredecessors()方法的判断,这个方法就是判断当前线程的前一个线程是否也有资格去获取锁,只有没有资格获取锁时,当前的线程才可以返回true.否则就不能获取锁,

 

  ReentrantLock(true):公平锁,排它锁,同一时刻,只允许一个线程访问

  ReenTrantLock(false):非公平锁,排它锁,同一时刻,只允许一个线程访问

  sychronized:非公平锁,可重入锁,排它锁,同一时刻,只允许一个线程访问

  ReadWriteTrantLock:共享锁,允许多线程读,但不允许多线程读、写;不允许多线程写、写操作;适用于多读的场景

超级参考:https://www.jianshu.com/p/f584799f1c77

 

 

二、ReenTrantLock

  ReenTrantLock是基于aqs实现的

2.1 ReentrantLock底层(CAS+queue)

  2.1.1 AQS

  AQS是abstractQueuedSynchronizer简称。AQS提供一种实现阻塞锁和FIFO队列的同步器的框架.AQS内部保存一个原子性的状态变量state,aqs中对state操作是原子的,通过cas修改该变量值,修改成功的线程表示获取到该锁,没有修改成功或者发现state已经是加锁状态则通过一个Waiter对象封装线程添加到等待队列中,并挂起等待被唤醒,

                                                     

  AQS同时提供排他模式(exclusive)和共享模式(shared)

 

 

 

  2.1.2 State

state状态值定义为线程获取该锁的重入次数,state为0表示当前没有被任何线程持有,state为1表示被其他线程持有,因为支持可重入,如果是持有锁的线程再次获取同一把锁,直接成功并且state状态值+1,线程释放锁state状态值-1,同理冲入多次锁的线程需要释放相应的次数

AQS中state类型:private volatile int state,表示当前同步状态,state访问方式有三种:getstate()、 setstate() 、compareAndSetState(),其中compareAndSetState()是依赖unsafe类的compareAndSwapInt()方法,代码实现如下:

 /**
     * The synchronization state.
     */
    private volatile int state;
  
    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }

    /**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a {@code volatile} read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

 

 3. ReentrantLock

ReentrantLock是一个可重入锁(可重入锁是指同一个线程可以多次获取这一把锁--注意是同一把锁,如果不是同一把不满足可重入性)。在释放锁之前该线程再次访问了该同步锁的其他方法时,这个线程不需要再次竞争锁便可持有该锁,只是需要记录重入次数+1.重入锁的设计目的是为了解决死锁的问题(所谓死锁是指多个进程在运行过程中因争夺资源而造成的一种僵局。比如threadA按照先获得锁a再获得锁b的顺序获得锁;threadB按照先获得锁b再获得锁a顺序获得锁,这样threadA和threadB就产生了死锁)

 可重入锁,简单来说就是一个线程如果抢占了互斥锁资源,在锁释放前再去竞争同一把锁的时候,不需要等待,只需要记录重入次数

在多线程并发编程里面,绝大部分锁都是可重入的,比如Synchronized(比如线程安全单例模式,就是使用二次synchronized锁实现的)、ReentrantLock等,但也有不支持重入的锁,比如JDK8里面提供的读写锁StampedLock

锁的可重入性,主要解决的问题是避免线程死锁的问题。

示例如下,两个线程都是同一个类锁,\两个线程间不会发送死锁情况

public class TestLock1 {

public static void main(String[] args) {
new Thread(() -> {
new LockClass().lock1();
}).start();
new Thread(() -> {
new LockClass().lock2();
}).start();
}
}
class LockClass{
public static synchronized void lock1(){
System.out.println("LockClass.lock1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockClass1.lock1();
}

public static synchronized void lock2(){
System.out.println("lock2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock1();
}
}

如上,由于一个已经获取同步锁X的线程,在释放锁X之前再去竞争锁X的时候,相当于会出现自己要等待自己释放锁,所以只有可重入锁才能解决类似死锁问题

 3.1 ReentrantLock作用范围

 Lock只加在代码块上,需要手动解锁,Lock锁住的是当前对象,作用范围是.lock()~.unlock()

 

public class ReentrantLockTest {
private static Lock lock=new ReentrantLock();
private static int count=0;

private static void inc(){
try{
// lock.lock(); //条件1
count++;
Thread.sleep(10);
desc();
}catch(Exception e){
e.printStackTrace();
}finally {
// lock.unlock(); //条件1
}
}

private static void desc(){
--count;
}
public static void main(String[] args) {
for (int i = 0; i < 20000; i++) {
new Thread(() -> {
inc();//1
}).start();
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count:"+count);
}
}

 

 上述代码执行结果:

count:212(每次执行结果可能不一样,因为线程不安全)

 当把条件1注释去掉后后每次执行结果:

count: 1

 

3.2 重入锁

如下例中红色标记部分

inc() 方法获取锁成功并没有释放锁的情况下调用dec()再次获取锁,假如没有重入锁的话这里会导致死锁。此线程如果再次访问了该同步锁的其他的方法,这个线程不需要再次竞争锁,只需要记录重入次数

public class ReentrantLockTest {
    private static Lock lock=new ReentrantLock();
    private static int count=1;

    private static void inc(){
        try{
            lock.lock();
            count++;
            Thread.sleep(10);
            desc();
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    private static void desc(){
        lock.lock();
        --count;
        lock.unlock();
    }
    public static void main(String[] args) {
        for (int i = 0; i < 20000; i++) {
            new Thread(() -> {
                inc();//1
            }).start();
        }
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count:"+count);
    }
}

 

 

 参考:https://www.jianshu.com/p/da9d051dcc3d

https://www.jianshu.com/p/7e2ec0eccd1e

 

 

  

 

posted on 2019-07-29 17:16  colorfulworld  阅读(264)  评论(0编辑  收藏  举报