zoukankan      html  css  js  c++  java
  • (四)Lock,ReentrantLock,ReentrantReadWriteLock类的使用以及相关api---synchronized进阶

    这篇博客记录了Lock,ReentrantLock,ReentrantReadWriteLock类的使用以及其一些api:

    码字不易~~另外《java多线程编程核心技术》这本书读着很爽

    前言说明:之前为了解决多线程时的非线程安全问题,使用的是synchronized。接下来记录的是他的升级版本ReentrantLock,更加灵活,可控性更高,而ReentrantReadWriteLock类是对ReentrantLock类的补充,能够在某些条件之间之下提交效率

    下面先来看下都有哪些api,以及和synchronized之间是怎样对应的吧。

     

    以前使用锁完成同步是将同步代码块写在synchronized之内,现在我们使用  

    Lock lock = new ReentrantLock();

    来声明一个锁,他有这两个方法

    lock.lock();  和 lock.unlock();   这两个是配套的,在其之间的代码就是同步代码块。

    和之前一样,lock()方法会让当前线程持有对象监听器,具体规则之类的和synchronized也一样,

    比如下面的例子,MyService有一段代码上锁,自定义线程类调用它

    MyService.java

    package 第四章;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class MyService {
        private Lock lock = new ReentrantLock();
        public void testMethod(){
            lock.lock();
            for(int i=0;i<5;i++){
                System.out.println(i+"线程:"+Thread.currentThread().getName());
            }
            lock.unlock();
        }
    }
    View Code

    MyThread.java

    package 第四章;
    
    public class MyThread extends Thread {
        private MyService myService;
    
        public MyThread(MyService myService) {
            super();
            this.myService = myService;
        }
    
        public void run(){
            myService.testMethod();
        }
    }
    View Code

    test.java

    package 第四章;
    
    public class test {
        public static void main(String[] args){
            MyThread[] threads = new MyThread[5];
            MyService myService = new MyService();
            for(int i=0;i<5;i++){
                threads[i] = new MyThread(myService);
                threads[i].start();
            }
        }
    }
    View Code

    运行结果:

     可以看到线程之间是同步执行的,当然前提是同一个MyService对象。

    之前的wait/notify,用Condition对象来替换:

    效率提高的地方以及原因:

    Condition对象可以对同一个锁声明多个,相当于每当让线程等待时,他都有自己的唤醒condition,换句话说,每一个线程都可以注册一个Condition,这样当我们唤醒线程的时候,就可以唤醒指定的线程,比如之前的生产者消费者模型之中的假死现象,我们使用过notifyAll()来解决的,但是这种方法唤醒了所有的线程,让所有线程都去争抢cpu,但是我们事实上指向唤醒异类线程,并不想唤醒同类,全部唤醒的话,效率是一个问题。那么现在,给每一个线程都注册

    一个Condition,这样子唤醒时候,我们就可以唤醒指定的线程,提高了效率,也更加灵活。

    下面的是一个简单的await/signal例子,展示了基本的使用:await类似之前的wait,signal类似于notify:signalAll()唤醒全部

    更改之前的MyService.java

    condition.await()让线程阻塞,condition.signal()随机唤醒一个由当前condition注册的线程

    package 第四章;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.Condition;
    public class MyService {
        private Lock lock = new ReentrantLock();
        public Condition condition = lock.newCondition();
        public void testMethod(){
            try{
            lock.lock();
            System.out.println("即将开始循环");
            condition.await();
            for(int i=0;i<2;i++){
                System.out.println(i+"线程:"+Thread.currentThread().getName());
            }
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        public void signal(){
            try{
                lock.lock();
                this.condition.signal();
                System.out.println("唤醒了一个线程");
            }finally {
                lock.unlock();
            }
        }
    }
    View Code

    MyThread.java不变

    test.java:先让线程全部阻塞,然后调用自定义的signal方法唤醒线程,

    package 第四章;
    
    public class test {
        public static void main(String[] args){
            MyThread[] threads = new MyThread[5];
            MyService myService = new MyService();
            for(int i=0;i<5;i++){
                threads[i] = new MyThread(myService);
                threads[i].start();
            }
            try{
                Thread.sleep(1000);
                myService.signal();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    View Code

    运行结果如下:

    可以看到,我们成功唤醒了一个线程。

    下面的例子唤醒了一个指定的线程

    MyService.java:根据当前线程的名字让指定的Condition对象等待,并书写两个唤醒不同的Condition对象注册的线程

    package 第四章;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.Condition;
    public class MyService {
        private Lock lock = new ReentrantLock();
        public Condition conditionA = lock.newCondition();
        public Condition conditionB = lock.newCondition();
        public void testMethod(){
            try{
                lock.lock();
                System.out.println("线程"+Thread.currentThread().getName()+"等待中...");
                if(Thread.currentThread().getName().equals("A"))
                    conditionA.await();
                else
                    conditionB.await();
                for(int i=0;i<2;i++){
                    System.out.println(i+"线程:"+Thread.currentThread().getName());
            }
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        public void signalA(){
            try{
                lock.lock();
                this.conditionA.signal();
                System.out.println("唤醒了A线程");
            }finally {
                lock.unlock();
            }
        }
        public void signalB(){
            try{
                lock.lock();
                this.conditionB.signal();
                System.out.println("唤醒了B线程");
            }finally {
                lock.unlock();
            }
        }
    }
    View Code

    test.java,启动A,B两个线程,只唤醒A线程

    package 第四章;
    
    public class test {
        public static void main(String[] args){
            MyService myService = new MyService();
            MyThread myThreadA = new MyThread(myService);
            myThreadA.setName("A");
            MyThread myThreadB = new MyThread(myService);
            myThreadB.setName("B");
            myThreadA.start();
            myThreadB.start();
            try{
                Thread.sleep(1000);
                myService.signalA();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    View Code

    运行结果:

    根据代码,我们可以看到可以通过不同Condition对象来唤醒指定的线程。

    用处:

    1.可以想到,如果用Lock来解决之前的多消费多生产者时的假死问题,我们可以将生产者统一注册一个Condition,消费者统一注册一个Condition,每一次唤醒对方的Condition,这样子就不会出现连续唤醒同类导致假死的情况了,并且可以避免唤醒所有线程,导致效率低下。

    2.我们也可以按照我们想要的顺序进行唤醒,只要你注册了正确的Condition对象

    公平锁和非公平锁:

    比较好理解,公平锁相当于一个队列,先进先出,先运行的线程先拿到锁,后运行的后拿到锁,按照顺序来,非公平锁就是锁的抢占是随机的,没有顺序。

    默认是非公平锁,创建Lock时加上true参数即为公平锁:

    Lock lock =new ReentrantLock(true);

     

    下面介绍一些ReentrantLock的api,

    一般在一些定制化的情况可能会用到,emmm,这块先了解一下,知道有这些就行,emmm,说实话目前我感觉这个没啥用,有个印象,不过注意使用这些API使,必须以下面这种方式new对象

    ReentrantLock lock = new ReentrantLock();

    (lock.)GetHoldCount():查询当前线程有保持着几个lock锁,简单来讲就是当前线程调用了几次lock()方法

    GetQueueLength():有多少个线程在等待获取当前锁,可以理解为有多少个没有拿到当前锁,

    getWaitQueueLength(Condition condition):有多少个线程处于阻塞状态,并且是执行了参数Condition对象所对应的await()方法导致阻塞的。

    hasQueuedThread(Thread thread):查询指定的线程是否正在等待获取当前锁

    hasQueuedThreads():查询是否有线程正在等待获取当前锁

    hasWaiters(Condition):查询是否有线程是由于调用了参数Condition.await()导致阻塞的。

    isHeldByCurrentThread():查询当前线程是否持有当前锁

    isLocked():当前锁是否被某个线程持有

    awaitUninterruptibly():这也是一种让当前线程阻塞的方法,不过await调用之后如果再使用Interrupt等代码阻塞当前进程会报异常,但是这个不会,相当于让当前线程变成可以阻塞的线程,,,,不懂有撒用

    awaitUntil(Date):阻塞当前线程,如果在指定时间之前还没有被唤醒,则唤醒他。参数也可以传Calendar.getTime(),Calendar类用于处理时间

    ReentrantReadWriteLock类

    之前的ReentrantLock相当于同一时间只有一个线程在执行代码。但是在不涉及更改实例变量的代码之中,我们可以允许异步运行来加快效率, 而一些涉及到更改实例变量的代码,这时候同步执行(这时候异步可能出现非线程安全),这样可以在一定程度上加快效率,这就是这个类的作用。

    简单来说,我们一般有读写两个操作,如果多个线程执行读操作,ok,异步执行,如果多个线程有的执行读,有的写,ok,同步执行,这个类就是自动完成这个事情,你只需要在锁时使用不同类型的锁就行。

    下面是一个例子,读读异步(其他全部同步):
    ReadAndWrite.java  代表具体的操作,读,写,输出当前操作以及时间,sleep()模拟操作耗费的时间

    package 第四章;
    
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class ReadAndWrite {
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        public void read(){
            try{
                lock.readLock().lock();
                System.out.println("读操作"+System.currentTimeMillis());
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                lock.readLock().unlock();
            }
        }
        public void write(){
            try{
                lock.writeLock().lock();
                System.out.println("写操作"+System.currentTimeMillis());
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                lock.writeLock().unlock();
            }
        }
    }
    View Code

    MyThread2.java:里面有两个java类,一个执行读操作,一个写操作

    package 第四章;
    
    class MyThreadRead extends Thread{
        private ReadAndWrite readAndWrite;
    
        public MyThreadRead(ReadAndWrite readAndWrite) {
            this.readAndWrite = readAndWrite;
        }
    
        public void run(){
            this.readAndWrite.read();
        }
    }
    class MyThreadWrite extends Thread{
        private ReadAndWrite readAndWrite;
    
        public MyThreadWrite(ReadAndWrite readAndWrite) {
            this.readAndWrite = readAndWrite;
        }
    
        public void run(){
            this.readAndWrite.write();
        }
    }
    View Code

    test.java:  创建三个读线程

    package 第四章;
    
    public class test {
        public static void main(String[] args){
               ReadAndWrite readAndWrite = new ReadAndWrite();
               MyThreadRead reads[] = new MyThreadRead[3];
               for(int i=0;i<3;i++) {
                   reads[i] = new MyThreadRead(readAndWrite);
                   reads[i].start();
               }
            }
        }
    View Code

    运行结果:

    可以看到,三个读操作时同时执行的。

    下面更改test.java,创建三个读线程,三个写线程:

    test.java

    package 第四章;
    
    public class test {
        public static void main(String[] args){
    
               ReadAndWrite readAndWrite = new ReadAndWrite();
            MyThreadWrite writes[] = new MyThreadWrite[3];
            for(int i=0;i<3;i++) {
                writes[i] = new MyThreadWrite(readAndWrite);
                writes[i].start();
            }
               MyThreadRead reads[] = new MyThreadRead[3];
               for(int i=0;i<3;i++) {
                   reads[i] = new MyThreadRead(readAndWrite);
                   reads[i].start();
               }
    
            }
        }
    View Code

    运行:

     

    可以看到,写操作之间是互斥的,相当于同步,一个一个执行的,读的时候就是异步的,

     ,,好嘞,就演示这几个,其他的都同理,只有读读是异步的,读写同步,你可以交替着start看一下,如下:

     好滴,第四章就这些暂时。。

  • 相关阅读:
    JAVA 大数据基本操作
    C++ set 基本操作
    JVM 线上故障排查基本操作
    Git基本常用命令
    Git 入门:概念、原理、使用
    30分钟学会如何使用Shiro
    做个男人,做个成熟的男人,做个有城府的男人
    Nginx的最基本功能以及简单配置
    博客网站
    单点登录原理与简单实现
  • 原文地址:https://www.cnblogs.com/eenio/p/11390950.html
Copyright © 2011-2022 走看看