Java~多线程ReentrantLock的公平锁和非公平锁 ,结合Condition实现部分路通知和选择路通知

ReentrantLock

  • 在Java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,但在JDK1.5中新增加了ReentrantLock类也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上也比 synchronized更加的灵活。

演示ReentrantLock的同步功能

  • 服务类
public class Service {

    private ReentrantLock reentrantLock = new ReentrantLock();

    public void methodA() {
        reentrantLock.lock();

        System.out.println(Thread.currentThread().getName() + " 进入lock");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
        System.out.println(Thread.currentThread().getName() + " 离开lock");

        reentrantLock.unlock();
    }
}
  • 运行类
public class Demo {

    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        //创建俩个线程
        Thread a = new Thread("A") {
            @Override
            public void run() {
                service.methodA();
            }
        };
        Thread b = new Thread("B") {
            @Override
            public void run() {
                service.methodA();
            }
        };
        a.start();
        b.start();
        a.join();
        b.join();
    }
}
  • 运行结果
    在这里插入图片描述
  • 从运行的结果来看,当前线程打印完毕之后将锁进行释放,其他线程才可以继续打印。线程打印的数据是分组打印,因为当前线程已经持有锁,但线程之间打印的顺序是随机的。

ReentrantLock的公平锁与非公平锁

  • 公平与非公平锁:锁Lock分为“公平锁”和“非公平锁”,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一一样的就是先来的不- -定先得到锁,这个方式可能造成某些线程一直拿不到锁, 结果也就是不公平的了。
代码演示
  • 服务类
public class Service2 {

    private ReentrantLock reentrantLock;

    public Service2(boolean isFire) {
        this.reentrantLock = new ReentrantLock(isFire);
    }

    public void method() {
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName() + " 获取锁");
        reentrantLock.unlock();
    }
}
  • 运行类
public class Demo2 {

    public static void main(String[] args) throws InterruptedException {
        //传入true表示公平, 传入false表示不公平
        Service2 service2 = new Service2(true);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {

                service2.method();
            }
        };
        //创建多个线程
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(runnable);
        }
        for (int i = 0; i < 10; i++) {
            threads[i].start();
        }
        for (int i = 0; i < 10; i++) {
            threads[i].join();
        }
    }
}

  • 当我们传入的是true表示公平锁 其运行结果为
    在这里插入图片描述

  • 当我们传入false表示不公平时运行结果为
    在这里插入图片描述

  • 从结果上就可以看出差距, 公平锁上锁的顺序是有序的, 还要注意ReentrantLock默认的是非公平锁

        **
         * 通过看源码可以发现, 如果是公平锁, 在lock的时候会走fairSync,
         * 然后判断此时队列中有没有等待的线程, 如果有就加入队列尾部保证公平性
         *         if (!tryAcquire(arg) &&
         *             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         *             selfInterrupt();
         *     }
         *
         * 如果不是公平的, 就会走NoFairSync, 直接CAS检查此时的state是不是为0, 如果此时正好为0就去执行了
         * 也就破坏了公平性
         *         final void lock() {
         *             if (compareAndSetState(0, 1))
         *                 setExclusiveOwnerThread(Thread.currentThread());
         *             else
         *                 acquire(1);
         *         }
         *

Condition对象监视器

  • 关键字synchronized与wait()和notifyO/notifyAllO)方法相结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助于Condition 对象。Condition 类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。在使用notify()/notifyAllO)方法进行通知时,被通知的线程却是由JVM随机选择的。但使用ReentrantLock结合Condition类是可以实现前面介绍过的“选择性通知”,这个功能是非常重要的,而且在Condition类中是默认提供的。
  • 而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll)时,需要通知所有的WAITING线程,没有选择权,会出现相当大的效率问题。

Condition的实现分析

  • 首先Condition的操作需要在获取到相关锁的条件下, 所以他是作为同步器的内部类的
  • 每个Condition对象内部都有一个队列, 是一个等待队列, 该队列是Condition对象实现等待 通知的功能关键
  • 这个等待队列是一个FIFO先进先出的队列, 在队列的每一个节点上都是一个线程的引用, 该线程就是Condition对象上等待的线程, 如果一个线程执行了Condition.await() 就会释放锁然后进入这个队列中, 等待通知的时候, 也就是执行Condition.signal() 方法时, 将会唤醒这个Condition对象的等待队列中的等待时间最长的那个线程, 也就是头结点引用的那个线程, 这个线程就会进入到同步队列中, 并将这个线程再等待队列中删掉
  • 使用Condition和synchronized一样都要在加锁的情况下使用, 否则会报异常

演示Condition的等待与通知

  • Object类中的wait()方法相当于Condition类中的await()方法。
  • Object类中的wait(long timeout)方法相当于Condition类中的await(long time, TimeUnit unit)方法。
  • Object类中的notify方法相当于Condition类中的signal(方法。
  • Object类中的notifyAll 方法相当于Condition类中的signalAll()方法。
  • 下面进行演示
  • 服务类
public class Service3 {

    private ReentrantLock reentrantLock = new ReentrantLock();
    private Condition condition = reentrantLock.newCondition();

    public void methodAwait() {
        System.out.println(Thread.currentThread().getName() + " 开始运行");
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName() + " 执行await");
        try {
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
            System.out.println(Thread.currentThread().getName() + " 运行结束");
        }
    }

    public void methodSignal() {
        System.out.println(Thread.currentThread().getName() + " 开始运行");
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName() + " 执行signal");
        try {
            condition.signal();
        } finally {
            reentrantLock.unlock();
            System.out.println(Thread.currentThread().getName() + " 运行结束");
        }
    }
}
  • 运行类
public class Demo3 {

    public static void main(String[] args) throws InterruptedException {
        Service3 service3 = new Service3();
        Thread a = new Thread("A") {
            @Override
            public void run() {
                service3.methodAwait();
            }
        };
        Thread b = new Thread("B") {
            @Override
            public void run() {
                service3.methodSignal();
            }
        };
        a.start();
        Thread.sleep(100);
        b.start();
    }
}
  • 运行结果
    在这里插入图片描述

实现部分路通知和选择路通知

  • 服务类
public class Service4 {

    private ReentrantLock reentrantLock = new ReentrantLock();
    private Condition conditionA = reentrantLock.newCondition();
    private Condition conditionB = reentrantLock.newCondition();

    public void awaitA() {
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName() + " 开始运行");
        System.out.println(Thread.currentThread().getName() + " 执行awaitA");
        try {
            conditionA.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + " 运行结束");
            reentrantLock.unlock();
        }
    }

    public void awaitB() {
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName() + " 开始运行");
        System.out.println(Thread.currentThread().getName() + " 执行awaitB");
        try {
            conditionB.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + " 运行结束");
            reentrantLock.unlock();
        }
    }

    public void signalA() {
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName() + " 开始运行");
        System.out.println(Thread.currentThread().getName() + " 执行signalA");

            conditionA.signal();
            System.out.println(Thread.currentThread().getName() + " 运行结束");
            reentrantLock.unlock();

    }

    public void signalAandB() {
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName() + " 开始运行");
        System.out.println(Thread.currentThread().getName() + " 执行signalAandB");

        conditionA.signal();
        conditionB.signal();
        System.out.println(Thread.currentThread().getName() + " 运行结束");
        reentrantLock.unlock();
    }

    public void signalB_All() {
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName() + " 开始运行");
        System.out.println(Thread.currentThread().getName() + " 执行signalB_All");

        conditionB.signalAll();
        System.out.println(Thread.currentThread().getName() + " 运行结束");
        reentrantLock.unlock();
    }
}
  • 运行类
public class Demo4 {

    public static void main(String[] args) throws InterruptedException {
        Service4 service4 = new Service4();
        //创建3个A线程, 三个B线程
        Thread[] threadsA = new Thread[3];
        Thread[] threadsB = new Thread[3];

        for (int i = 0; i < 3; i++) {
            threadsA[i] = new Thread("A" + i) {
                @Override
                public void run() {
                    service4.awaitA();
                }
            };
            threadsB[i] = new Thread("B" + i) {
                @Override
                public void run() {
                    service4.awaitB();
                }
            };
        }

        for (int i = 0; i < 3; i++) {
            threadsA[i].start();
            threadsB[i].start();
        }
        Thread.sleep(100);

        System.out.println();
        System.out.println("signal一个A线程");
        service4.signalA();
        Thread.sleep(100);
        System.out.println();
        System.out.println("signal一个A一个B");
        service4.signalAandB();
        Thread.sleep(100);
        System.out.println();
        System.out.println("signalAll所有的B线程");
        service4.signalB_All();
        Thread.sleep(100);
        System.out.println();
        System.out.println("最后再signal一个A线程");
        service4.signalA();
    }
}
  • 运行结果
    在这里插入图片描述
    在这里插入图片描述
  • 可以得知,使用ReentrantLock结合Condition对象可以唤醒指定种类的线程,这是控制部分线程行为的方便方式。
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值