java多线程-Lock(八)

java多线程-Lock(八)

多线程使用synchronized来保持线程之间同步互斥,jdk1.5中加入了Lock对象也能实现同步效果
ReentrantLock(rɪ’entrənt)类的使用
ReentrantReadWriteLock类的使用

ReentrantLock

java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

lock,unlock的使用

代码段:使用ReentrantLock

package cn.thread.lock.reentrant;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService {

    private Lock lock = new ReentrantLock();

    public void serviceA() {
        lock.lock();//serviceA ,serviceB 中的lock是同一把锁哦,谁先获取谁就有控制权。
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "----Ai=" + i);
        }
        lock.unlock();
    }

    public void serviceB() {
        lock.lock();
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "----Bi=" + i);
        }
        lock.unlock();
    }
}

public class ReentrantDome1 {

    public static void main(String orgs[]) {
        final MyService start = new MyService();
        new Thread(new Runnable() {
            public void run() {
                start.serviceA();

            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceB();

            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceA();

            }
        }).start();
    }
}

lock.lock();lock.unlock();包起来的代码,就和synchronized是一样的效果。
同一个lock对象锁是同一把;代码中serviceA ,serviceB 中的lock是同一把锁哦,谁先获取谁就有控制权。

lock.unlock最好还是放在finally中,这样才能安全的解锁,不然lock住的线程会一直阻塞。

await(),signal() 线程通信

package cn.thread.lock.reentrant;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService2 {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void serviceA() {
        try {
            lock.lock();
            condition.await();//线程挂起,进入等待中,和wait一样。
            System.out.println(Thread.currentThread().getName() + "----Ai=");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void serviceB() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "----Bi=");
            condition.signal();//唤醒被await的线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}


public class ReentrantDome2 {

    public static void main(String orgs[]) {
        final MyService2 start = new MyService2();
        new Thread(new Runnable() {
            public void run() {
                start.serviceA();

            }
        }).start();


        new Thread(new Runnable() {
            public void run() {
                start.serviceB();

            }
        }).start();


    }
}

结果说明:
A线程先运行,获取到了锁,但是进入await之后,交出锁权限,线程挂起;B线程获取到锁,B线程唤醒A线程,B线程运行完毕,A线程接着运行。

lock的await与,signal,signalAll与对象的wait,notify,notifyAll一毛一样。

多个Condition.await

lock 可以拥有多个Condition,每个Condition可以独自拥有自己的await,signal。

package cn.thread.lock.reentrant;


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService3 {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    private Condition condition2 = lock.newCondition();

    public void serviceA() {
        try {
            lock.lock();
            condition.await();//线程挂起,进入等待中,和wait一样。
            System.out.println(Thread.currentThread().getName() + "----Ai=");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void serviceB() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "----Bi=");
            condition.signal();//唤醒被await的线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void serviceC() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "----Ci=");
            condition2.signal();//condition2的等候,只有condition2.signal才能唤醒哦
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void serviceD() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "----Di=");
            condition2.signal();//唤醒被condition2.await的线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}
public class ReentrantDome3 {

    public static void main(String orgs[]) {
        final MyService3 start = new MyService3();
        //线程一
        new Thread(new Runnable() {
            public void run() {
                start.serviceA();

            }
        }).start();

        //线程二
        new Thread(new Runnable() {
            public void run() {
                start.serviceD();

            }
        }).start();

    }
}

说明:

  • 线程2中调用了serviceD方法,唤醒的condition2中的await;但是线程1中的await是condition,所有线程2无法唤醒线程1中的等待。
    线程1继续挂起。
  • 可以看到线程1与线程2还是同一把锁,lock还是同一个,只不过他们condition2不一样而已。如果线程1没有await,那么线程1,2还是存在竞争锁资源的。(他们两个一开始就在竞争锁资源,只不过线程1先获取到了,但是遇到await后线程挂起,交出了锁权限)

可中断锁

lock与synchronized还有一个很大的区别,就是lock是可中断的。我们来看看这个是怎么个中断的

扫描二维码关注公众号,回复: 2681036 查看本文章

public class ReentrantDome4 {

    private Lock lock = new ReentrantLock();

    public void serviceA() {
        try {
            //lock.lock();
            //可中断,响应中断。这个和lock是一样的,只不过在lock()的基础上加了一个判断,用来响应线程的中断。
            lock.lockInterruptibly();
            //1.加入时间等待
            Thread.sleep(500);
            System.out.println("lock了。。。。。" + Thread.currentThread().getId());

        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("中断了.....");
        } finally {
            lock.unlock();
        }
    }


    public static void main(String[] args) {

        final ReentrantDome4 dome4 = new ReentrantDome4();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("threadA=" + Thread.currentThread().getId());
                dome4.serviceA();

            }
        });
        t1.start(); 

        //2加入时间等待
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("threadB=" + Thread.currentThread().getId());
                dome4.serviceA();

            }
        });
        t2.start();
        t2.interrupt(); //如果把这个去了,相对来说lock.lockInterruptibly()就和lock.lock()功能一样了。
    }


}

解说:线程t1开始执行,t1获取到了锁,这个时候t2.start()开始创建线程,t2.interrupt开始执行线程状态改为停止状态。
t2开始尝试获取锁,发现线程被标记为停止了,就抛异常了,不在继续往下执行。

让interrupt无效的写法

Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("threadB=" + Thread.currentThread().getId());
                dome4.serviceA();

            }
        });
        t2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t2.interrupt(); 

t2.开始执行,然后t2.interrupt修改状态是无效的,为什么,因为t2已经执行完毕了,t2.interrupt修改状态已经无效了。

看下lock.lockInterruptibly的源码?

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted()) //检查线程是否被停止了,如果停止则抛异常,不在往下之心
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

lockInterruptibly()在与interrupt()配套时lockInterruptibly才有它的作用,不然lockInterruptibly和lock就没有区别了。

公平锁与非公平锁

Lock锁分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得FIFO。而非公平锁就是一种获取锁的抢占机制,随机获得锁。
private Lock lock = new ReentrantLock();默认是非公平锁
private Lock lock = new ReentrantLock(true);公平锁。

其他方法

getHoldCount():查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
getQueueLength():返回正在等待获取此锁定的线程估计数。比如有10个线程,一个线程先获取了lock锁权限,其他9个线程就需要等待lock释放锁,这个时候返回9.
getWaitQueueLange(Condition condition):查询await等待队列数。

hasQueuedThread(Thread thread):查询thread线程是否正在等待获取此锁定。
hasQueuedThread():查询是否有线程正在等待获取此锁定。
hasWaiters(Condition condition):是否有线程正在等待与此锁定有关的condition条件。
lockInterruptibly():如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。
tryLock(): 仅在调用时锁定未被锁另一个线程保持的情况下,才获取该锁定
tryLock(long timeout,TimeUnit unit)

ReentrantReadWriteLock的使用

ReentrantReadWriteLock字面意思可以看出是一个读写锁。
ReentrantReadWriteLock有两种锁,一种是读锁,称为共享锁,一个种是写锁为互斥锁。

读写锁的特性:

  • 可重入:允许读锁可重入,写锁可重入,但是写锁可以获得读锁,读锁不能获得写锁。 注意:在锁重入中,读锁不能获取写锁
  • 锁降级:允许写锁降低为读锁,就是在锁重入中,写锁重入到了读锁中.称为锁降级.
  • 中断锁的获取:在读锁和写锁的获取过程中支持中断
  • 支持Condition:写锁提供Condition实现
  • 监控:提供确定锁是否被持有等辅助方法

读读共享

package cn.thread.lock.reentrant;


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyService4 {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private int i = 0;

    public void serviceA() {
        try {
            lock.readLock().lock();

            i++;
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName() + "----Ai=" + "i=" + i);
        } catch (Exception e) {

        } finally {
            lock.readLock().unlock();
        }
    }

    public void serviceB() {
        try {
            lock.readLock().lock();
            i++;
            System.out.println(Thread.currentThread().getName() + "----Bi=" + "i=" + i);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }

    }
}

public class ReentrantWirteReadDome1 {
    public static void main(String orgs[]) {
        final MyService4 start = new MyService4();
        new Thread(new Runnable() {
            public void run() {
                start.serviceA();

            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                start.serviceB();

            }
        }).start();



    }
}

输出:
Thread-1—-Bi=i=2
Thread-0—-Ai=i=2

线程1将i加1后,开始休眠100毫秒,线程2这个时候将i加1后变为2;接着两个同时输出i=2;
使用线程1和线程2都使用的是读锁,所以不会加锁.

写写,读写,写读互斥

public void serviceB() {
        try {
            lock.writeLock().lock();
            i++;
            System.out.println(Thread.currentThread().getName() + "----Bi=" + "i=" + i);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }

    }

输出结果:
Thread-0—-Ai=i=1
Thread-1—-Bi=i=2

将serviceB改为写锁时,serviceA与serviceB为互斥锁(它们都是同一把锁哦)
线程A先执行serviceA方法,将i++后变为1,睡眠100毫秒,线程B执行serviceB,但是由于读写锁互斥,所以B线程被阻塞,A线程睡眠100ms后接着执行;A线程执行完毕后,释放锁,B线程获取到锁,B开始执行i++变为2.

锁可重入

public class ReentrantReadWriteLockDemo2 {

    public static void main(String[] args) {
        final ReentrantTest test = new ReentrantTest();
        new Thread(new Runnable() {
            public void run() {
                test.serviceA();

            }
        }).start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.serviceB();
            }
        }).start();

    }


    public static class ReentrantTest {
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

        private volatile int i = 0;

        public void serviceA() {
            try {
                System.out.println("serviceA开始获取读锁=====");
                lock.readLock().lock();
                System.out.println("serviceA得到读锁=====");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "----Ai=" + "i=" + i);
                serviceA1();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
        }

        public void serviceA1() {
            try {
                System.out.println("write1开始获取重入写锁=====");
                lock.writeLock().lock();
                System.out.println("write2得到写锁=======");
                i++;
                Thread.sleep(3000); 
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        }

        public void serviceB() {
            try {
                System.out.println("read2serviceb获取读锁");
                lock.readLock().lock();
                System.out.println("read2serviceb得到读锁");
                System.out.println(Thread.currentThread().getName() + "----Bi=" + "i=" + i);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println();
                lock.readLock().unlock();
            }
        }
    }
}

结果:
serviceA开始获取读锁=====
serviceA得到读锁=====
read2serviceb获取读锁
read2serviceb得到读锁
Thread-1----Bi=i=0
Thread-0----Ai=i=0
write1开始获取重入写锁=====

解说:serviceA,serviceB都是读锁,所以serviceA获取锁还在执行的过程中,sercieB也同样可以获取锁.他们两个一起执行了.
这里虽然没有死锁,但是基本和锁死差不多.线程1先进入执行了serviceA(读锁)然后serviceA调用了serviceA1(写锁)程序开始不往下执行了.因为读锁中是不能重入写锁的(原因看源码可知)

如果是serviceA调用serviceB是可以的,他们读是读锁;serviceA1调用serviceB是可以的,这就是锁降级.

高性能缓存设计

你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
这个时候就可以使用我们的读写锁来实现了。


class MyService4 {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private int i = 0;

    public void serviceA() {
        try {
            lock.readLock().lock();
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName() + "----Ai=" + "i=" + i);
        } catch (Exception e) {

        } finally {
            lock.readLock().unlock();
        }
    }

    public void serviceB() {
        try {
            lock.writeLock().lock();
            i++;
            System.out.println(Thread.currentThread().getName() + "----Bi=" + "i=" + i);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
}

public class ReentrantWirteReadDome1 {
    public static void main(String orgs[]) {
        final MyService4 start = new MyService4();

        new Thread(new Runnable() {
            public void run() {
                start.serviceA();

            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                start.serviceA();

            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceB();

            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceA();

            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                start.serviceA();

            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                start.serviceB();

            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                start.serviceB();

            }
        }).start();
    }
}

输出结果:
Thread-1----Ai=i=0
Thread-0----Ai=i=0
Thread-2----Bi=i=1
Thread-3----Ai=i=1
Thread-4----Ai=i=1
Thread-5----Bi=i=2
Thread-6----Bi=i=3

解说:可以看到线程0,1,3,4都是执行的A方法,他们都是读取值,所以0,1,3,4之间没有互斥。

  1. 0,1两个线程都是读,所以没有互斥,他们同时执行了。
  2. 线程2是写锁,由于读写是互斥的,所以他必须等待0,1线程释放锁才能开始写。
  3. 线程0,1释放锁后,线程2获取到了锁,3,4必须阻塞,因为写读也是互斥锁。所以3,4必须等待2写完后释放锁。
  4. 3,4都是读锁,所以他们之间没有互斥效果,一起执行了。线程5是写锁,由于3,4获取到了锁,所以5必须等待3,4执行完毕。
  5. 5,6都是写锁,他们必须顺序执行。因为写写互斥。

这里就是一个很好的读写分离。如果只有一个线程写,其他用户都是读, 那么多个线程一起读没有必要加锁,这样就提高的读的性能。但是读写,写读,读读都是互斥的,所以在读的时候不能写,在写的时候不能读,这样就能很好的保持多线程一致性。

ReentrantReadWriteLock源码分析

不管是ReentrantReadWriteLock还是 ReentrantLock 都是实现了lock接口,然后内部使用sync,底层使用AQS来实现加锁逻辑的.只是ReentrantReadWriteLock有两把锁,一把都锁,一把写锁.
那就来看看ReentrantReadWriteLock是怎么实现读写分类的.

1.需要先来看下这几个变量
         static final int SHARED_SHIFT   = 16;  
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);   // 2的16次方
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;  // 2的16次方-1 = 65535
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;   
        /** Returns the number of shared holds represented in count  */  
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }  //无符号右移16位,得到高16位的值
        /** Returns the number of exclusive holds represented in count  */  
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }   //

(转载)
比如,现在当前,申请读锁的线程数为13个,写锁1个,那state怎么表示?
ReentrantReadWriteLock是用一个32位的int类型来表示读写状态的,怎么表示呢?就是高16位表示读锁线程数,低16位表示写锁状态.
读锁13的二进制为 1101,写锁1的二进制就是1, 那state的二进制表示为00000000 00001101 00000000 00000001,十进制数为851969, 接下在具体获取锁时,需要根据这个851968这个值得出上文中的 13 与 1。
高位运算(读锁状态): 要算成13,只需要将state 无符号向右移位16位置,得出00000000 00001101,就出13(sharedCount这个方法).
低位运算(写锁状态): 根据851969要算成低16位置,只需要用该00000000 00001101 00000000 00000001 & 111111111111111,就可以得出00000001.

2.读锁
protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState(); //获取一下状态
            //exclusiveCount(c) ,查看是否有写锁并且当前线程不是自己,则返回-1,进入等待队列.
            //从这里可以看出来,只有当前对象有写锁的情况下,才会进入阻塞队列,如果都是读锁,则不会进入阻塞队列.
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);   //获取读锁的状态
            //readerShouldBlock查看是否需要加锁
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != current.getId())
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

        //这里基本就是一个自旋,不断尝试获取锁;如果没有写的的情况下,读锁不会线程阻塞,线程挂起
        final int fullTryAcquireShared(Thread current) { 
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                    //如果需要阻塞,说明除了当前线程持有写锁外,还有其他线程已经排队在申请写锁,故,
                    //即使申请读锁的线程已经持有写锁(写锁内部再次申请读锁,俗称锁降级)还是会失败,
                    //因为有其他线程也在申请写锁,此时,只能结束本次申请读锁的请求,转而去排队,否则,将造成死锁。
                } else if (readerShouldBlock()) {
                    // Make sure we're not acquiring read lock reentrantly
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != current.getId()) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();  //从readHolds中移除当前线程的持有数,然后返回-1,结束尝试获取锁步骤(结束tryAcquireShared 方法)然后去排队获取。
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != current.getId())
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }
3.写锁
protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState(); //获取状态
            int w = exclusiveCount(c); //获取写锁个数 
            if (c != 0) {  //当前已经存在读锁或者写锁了.
                // (Note: if c != 0 and w == 0 then shared count != 0)
                //没有写锁或者当前线程不是自己,返回false,进入等待队列;
                //假如w==0,则说明没有写锁,那么c!=0就表示当前有写锁,返回false进入阻塞状态.
                //假如w!=0,表示当前有写锁,但是当前线程不是自己,说明写锁是别人,则false进入阻塞状态.
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                //w!=0,并且写锁就是自己,则状态加1,表示锁重入;(只有在锁重入的情况下才会进入到这步哦)如果是第一次获取到锁,那么c=0才对.
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                //获取锁,设置写锁状态.
                setState(c + acquires);
                return true;
            }
            //c状态=0,表示没有写锁,没有读锁,开始使用cas机制竞争锁,如果竞争到,则将当前线程设置为自己,如果竞争失败,则返回false,进入等待队列.
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

写锁的逻辑很简单:当前没有写锁,读锁,则尝试获取锁.如果有写锁或者读锁了,则进入阻塞队列.

根据源码解说一下上面提出的:读锁不能重入写锁的原因.(锁可重入实例中)
1.线程1调用了serviceA(读锁),sercieA调用了serviceA1(写锁);
2.sercieA获取到锁后,c!=0;
3.进入第一个if,这个时候没有写锁,所以w=0直接返回false.写锁进入等待队列,当前线程1被挂起.
4.线程1进入了等待中,需要其他锁解锁时唤醒自己,但是其他线程根本就不能获取到锁了;因为其他读锁需要获取锁时发现有写锁状态,自己进入阻塞中;其他写锁获取锁时发现有读锁,自己进入阻塞中.就这样线程就一直处在等待中.
这里有没有感觉到死锁,最大的问题是这个死锁还不能被jstack分析出来.因为他并没有死锁,只是在等待解锁中.

什么时候选择用 ReentrantLock 代替 synchronized

既然如此,我们什么时候才应该使用 ReentrantLock 呢?答案非常简单 —— 在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。

结束语

Lock 框架是同步的兼容替代品,它提供了 synchronized 没有提供的许多特性,它的实现在争用下提供了更好的性能。但是,这些明显存在的好处,还不足以成为用 ReentrantLock 代替 synchronized 的理由。相反,应当根据您是否 需要 ReentrantLock 的能力来作出选择。大多数情况下,您不应当选择它 —— synchronized 工作得很好,可以在所有 JVM 上工作,更多的开发人员了解它,而且不太容易出错。只有在真正需要 Lock 的时候才用它。在这些情况下,您会很高兴拥有这款工具。

在《深入理解java虚拟机》书中也提到过,synchronized在jdk1.6之后有了很大的性能提升,几乎和reentrantLock性能差不多了,而且在jdk开发团队也说过,尽量使用原生语法synchronized,他们会在后续中继续优化。而且jdk1.6中加入了偏向锁,轻量级锁,自旋锁,自适应自旋锁,等优化。所以在没必要纠结的情况下尽量使用synchronized。

参考文献:

https://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html
《java多线程编程核心技术》
https://blog.csdn.net/prestigeding/article/details/53286756

猜你喜欢

转载自blog.csdn.net/piaoslowly/article/details/81476047