暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

线程同步原来这么简单【结尾篇】

java小哥 2021-10-28
215

线程同步中有七种同步方式,上一篇

线程同步

射手座的程序员,公众号:射手座的程序员线程同步原来这么简单【synchronized,volatile】

讲述了四种,剩下三种,大家可以继续深入了解下:

Lock 接口

Lock是java.util.concurrent.locks包下的接口,源码如下,注意相关接口:

    package java.util.concurrent.locks;
    import java.util.concurrent.TimeUnit;

    *
    * @see ReentrantLock
    * @see Condition
    * @see ReadWriteLock
    *
    * @since 1.5
    * @author Doug Lea
    */
    public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
    }

    需要注意的类:

      ReentrantLock:
      public class ReentrantLock implements Lock, java.io.Serializable {}

      Condition :接口
      public interface Condition{}

      ReadWriteLock 接口
      public interface ReadWriteLock{}

      ReentrantReadWriteLock:
      public class ReentrantReadWriteLock
      implements ReadWriteLock, java.io.Serializable{}

      Lock的特性:

        1).Lock不是Java语言内置的;

        2).synchronized是在JVM层面上实现的,如果代码执行出现异常,JVM会自动释放锁,但是Lock不行,要保证锁一定会被释放,就必须将unLock放到finally{}中(手动释放);

        3).在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetarntLock,但是在很激烈的情况下,synchronized的性能会下降几十倍;

        4).ReentrantLock增加了锁:

          a. void lock(); 无条件的锁;

          b. void lockInterruptibly throws InterruptedException;//可中断的锁;

      解释:使用ReentrantLock如果获取了锁立即返回,如果没有获取锁,当前线程处于休眠状态,直到获得锁或者当前线程可以被别的线程中断去做其他的事情;但是如果是synchronized的话,如果没有获取到锁,则会一直等待下去;

          c. boolean tryLock();//如果获取了锁立即返回true,如果别的线程正持有,立即返回false,不会等待;

          d. boolean tryLock(long timeout,TimeUnit unit);//如果获取了锁立即返回true,如果别的线程正持有锁,会等待参数给的时间,在等待的过程中,如果获取锁,则返回true,如果等待超时,返回false;

      在这里要介绍的是Condition:

      Condition的特性:

          1.Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的这些方法是和同步锁捆绑使用的;而Condition是需要与互斥锁/共享锁捆绑使用的。

          2.Condition它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。

          例如,假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒”读线程”;当从缓冲区读出数据之后,唤醒”写线程”;并且当缓冲区满的时候,”写线程”需要等待;当缓冲区为空时,”读线程”需要等待。

           如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒”读线程”时,不可能通过notify()或notifyAll()明确的指定唤醒”读线程”,而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。但是,通过Condition,就能明确的指定唤醒读线程。

      示例代码

        package com.blog.spring.thread;

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

        public class ConditionTask {

        private final Lock lock = new ReentrantLock();

        private final Condition addCondition = lock.newCondition();

        private final Condition subCondition = lock.newCondition();

        private static int num = 0;
        private List<String> lists = new LinkedList<String>();

        public void add() {
        lock.lock();

        try {
        System.out.println("==============当前添加线程:"+Thread.currentThread().getName()+"================");
        while(lists.size() == 3) {//当集合已满,则"添加"线程等待
        System.out.println("当前集合大小为: " + lists.size()+"-->集合已满,添加线程"+Thread.currentThread().getName()+"开始等待,去唤醒减少线程");
        this.subCondition.signal();
        addCondition.await();
        }

        num++;
        String str="元素" + num;
        lists.add(str);
        System.out.println("添加 [" + str + "]");
        System.out.println("当前集合大小为: " + lists.size());
        System.out.println("当前添加线程为: " + Thread.currentThread().getName());
        System.out.println("==============去唤醒减少线程================");
        this.subCondition.signal();

        } catch (InterruptedException e) {
        e.printStackTrace();
        } finally {//释放锁
        lock.unlock();
        }
        }


        public void sub() {
        lock.lock();

        try {
        System.out.println("==============当前减少线程:"+Thread.currentThread().getName()+"================");
        while(lists.size() == 0) {//当集合为空时,"减少"线程等待
        System.out.println("当前集合大小为: " + lists.size()+"-->集合为空,减少线程"+Thread.currentThread().getName()+"开始等待,去唤醒添加线程");
        addCondition.signal();
        subCondition.await();
        }

        num--;
        String str = lists.get(0);
        lists.remove(0);
        System.out.println("移除 [" + str + "]");
        System.out.println("当前集合大小为: " + lists.size());
        System.out.println("当前减少线程为: " + Thread.currentThread().getName());
        System.out.println("===============去唤醒添加线程===============");
        addCondition.signal();

        } catch (InterruptedException e) {
        e.printStackTrace();
        } finally {
        lock.unlock();
        }
        }


        }



        //测试类
        package com.blog.spring.thread;

        public class TestCondition {

        public static void main(String[] args) {

        final ConditionTask task = new ConditionTask();

        final ConditionTask task = new ConditionTask();

        for (int i = 0; i < 10; i++) {
        new Thread(new Runnable() {
        @Override
        public void run() {
        task.add();
        }
        },"添加线程"+i).start();
        }

        for (int i = 0; i < 10; i++) {
        new Thread(new Runnable() {
        @Override
        public void run() {
        task.sub();
        }
        },"减少线程"+i).start();
        }
        }

        运行结果:

          ==============当前添加线程:添加线程0================
          添加 [元素1]
          当前集合大小为: 1
          当前添加线程为: 添加线程0
          ==============去唤醒减少线程================
          ==============当前添加线程:添加线程6================
          添加 [元素2]
          当前集合大小为: 2
          当前添加线程为: 添加线程6
          ==============去唤醒减少线程================
          ==============当前添加线程:添加线程9================
          添加 [元素3]
          当前集合大小为: 3
          当前添加线程为: 添加线程9
          ==============去唤醒减少线程================
          ==============当前添加线程:添加线程1================
          当前集合大小为: 3-->集合已满,添加线程添加线程1开始等待,去唤醒减少线程
          ==============当前添加线程:添加线程2================
          当前集合大小为: 3-->集合已满,添加线程添加线程2开始等待,去唤醒减少线程
          ==============当前添加线程:添加线程3================
          当前集合大小为: 3-->集合已满,添加线程添加线程3开始等待,去唤醒减少线程
          ==============当前添加线程:添加线程4================
          当前集合大小为: 3-->集合已满,添加线程添加线程4开始等待,去唤醒减少线程
          ==============当前添加线程:添加线程5================
          当前集合大小为: 3-->集合已满,添加线程添加线程5开始等待,去唤醒减少线程
          ==============当前添加线程:添加线程7================
          当前集合大小为: 3-->集合已满,添加线程添加线程7开始等待,去唤醒减少线程
          ==============当前减少线程:减少线程3================
          移除 [元素1]
          当前集合大小为: 2
          当前减少线程为: 减少线程3
          ===============去唤醒添加线程===============
          ==============当前添加线程:添加线程8================
          添加 [元素3]
          当前集合大小为: 3
          当前添加线程为: 添加线程8
          ==============去唤醒减少线程================
          ==============当前减少线程:减少线程0================
          移除 [元素2]
          当前集合大小为: 2
          当前减少线程为: 减少线程0
          ===============去唤醒添加线程===============
          ==============当前减少线程:减少线程1================
          移除 [元素3]
          当前集合大小为: 1
          当前减少线程为: 减少线程1
          ===============去唤醒添加线程===============
          ==============当前减少线程:减少线程2================
          移除 [元素3]
          当前集合大小为: 0
          当前减少线程为: 减少线程2
          ===============去唤醒添加线程===============
          ==============当前减少线程:减少线程4================
          当前集合大小为: 0-->集合为空,减少线程减少线程4开始等待,去唤醒添加线程
          添加 [元素1]
          当前集合大小为: 1
          当前添加线程为: 添加线程1
          ==============去唤醒减少线程================
          ==============当前减少线程:减少线程5================
          移除 [元素1]
          当前集合大小为: 0
          当前减少线程为: 减少线程5
          ===============去唤醒添加线程===============
          ==============当前减少线程:减少线程6================
          当前集合大小为: 0-->集合为空,减少线程减少线程6开始等待,去唤醒添加线程
          ==============当前减少线程:减少线程7================
          当前集合大小为: 0-->集合为空,减少线程减少线程7开始等待,去唤醒添加线程
          ==============当前减少线程:减少线程8================
          当前集合大小为: 0-->集合为空,减少线程减少线程8开始等待,去唤醒添加线程
          ==============当前减少线程:减少线程9================
          当前集合大小为: 0-->集合为空,减少线程减少线程9开始等待,去唤醒添加线程
          添加 [元素1]
          当前集合大小为: 1
          当前添加线程为: 添加线程2
          ==============去唤醒减少线程================
          添加 [元素2]
          当前集合大小为: 2
          当前添加线程为: 添加线程3
          ==============去唤醒减少线程================
          添加 [元素3]
          当前集合大小为: 3
          当前添加线程为: 添加线程4
          ==============去唤醒减少线程================
          当前集合大小为: 3-->集合已满,添加线程添加线程5开始等待,去唤醒减少线程
          移除 [元素1]
          当前集合大小为: 2
          当前减少线程为: 减少线程4
          ===============去唤醒添加线程===============
          添加 [元素3]
          当前集合大小为: 3
          当前添加线程为: 添加线程7
          ==============去唤醒减少线程================
          移除 [元素2]
          当前集合大小为: 2
          当前减少线程为: 减少线程6
          ===============去唤醒添加线程===============
          移除 [元素3]
          当前集合大小为: 1
          当前减少线程为: 减少线程7
          ===============去唤醒添加线程===============
          移除 [元素3]
          当前集合大小为: 0
          当前减少线程为: 减少线程8
          ===============去唤醒添加线程===============
          当前集合大小为: 0-->集合为空,减少线程减少线程9开始等待,去唤醒添加线程
          添加 [元素1]
          当前集合大小为: 1
          当前添加线程为: 添加线程5
          ==============去唤醒减少线程================
          移除 [元素1]
          当前集合大小为: 0
          当前减少线程为: 减少线程9
          ===============去唤醒添加线程===============


          读写锁很重要,特在此保留一份:

            package com.blog.spring.thread;

            import java.util.Random;
            import java.util.concurrent.locks.ReadWriteLock;
            import java.util.concurrent.locks.ReentrantReadWriteLock;

            /**
            * Created by hxf on 17/3/29.
            */
            public class ReadWriteLockTest {

            public static void main(String[] args) {
            final Data data = new Data();
            for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
            public void run() {
            for (int j = 0; j < 5; j++) {
            data.set(new Random().nextInt(30));
            }
            }
            }).start();
            }
            for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
            public void run() {
            for (int j = 0; j < 5; j++) {
            data.get();
            }
            }
            }).start();
            }
            }

            static class Data {
            private int data;// 共享数据
            private ReadWriteLock rwl = new ReentrantReadWriteLock();
            public void set(int data) {
            rwl.writeLock().lock();// 取到写锁
            try {
            System.out.println(Thread.currentThread().getName() + "准备写入数据");
            try {
            Thread.sleep(20);
            } catch (InterruptedException e) {
            e.printStackTrace();
            }
            this.data = data;
            System.out.println(Thread.currentThread().getName() + "写入" + this.data);
            } finally {
            rwl.writeLock().unlock();// 释放写锁
            }
            }

            public void get() {
            rwl.readLock().lock();// 取到读锁
            try {
            System.out.println(Thread.currentThread().getName() + "准备读取数据");
            try {
            Thread.sleep(20);
            } catch (InterruptedException e) {
            e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "读取" + this.data);
            } finally {
            rwl.readLock().unlock();// 释放读锁
            }
            }
            }
            }

            运行结果:

              Thread-0准备写入数据
              Thread-0写入9
              Thread-0准备写入数据
              Thread-0写入15
              Thread-0准备写入数据
              Thread-0写入14
              Thread-1准备写入数据
              Thread-1写入17
              Thread-1准备写入数据
              Thread-1写入27
              Thread-1准备写入数据
              Thread-1写入26
              Thread-1准备写入数据
              Thread-1写入22
              Thread-1准备写入数据
              Thread-1写入19
              Thread-2准备写入数据
              Thread-2写入18
              Thread-2准备写入数据
              Thread-2写入6
              Thread-2准备写入数据
              Thread-2写入16
              Thread-2准备写入数据
              Thread-2写入18
              Thread-2准备写入数据
              Thread-2写入29
              Thread-3准备读取数据
              Thread-4准备读取数据
              Thread-5准备读取数据
              Thread-5读取29
              Thread-4读取29
              Thread-3读取29
              Thread-0准备写入数据
              Thread-0写入24
              Thread-0准备写入数据
              Thread-0写入16
              Thread-5准备读取数据
              Thread-4准备读取数据
              Thread-3准备读取数据
              Thread-3读取16
              Thread-4读取16
              Thread-5读取16
              Thread-4准备读取数据
              Thread-3准备读取数据
              Thread-5准备读取数据
              Thread-4读取16
              Thread-5读取16
              Thread-3读取16
              Thread-5准备读取数据
              Thread-4准备读取数据
              Thread-3准备读取数据
              Thread-5读取16
              Thread-3读取16
              Thread-4读取16
              Thread-3准备读取数据
              Thread-5准备读取数据
              Thread-4准备读取数据
              Thread-5读取16
              Thread-3读取16
              Thread-4读取16
              Process finished with exit code 0

              思考:比较Condition与ReadWriteLock(ReentrantReadWriteLock是实现类)运行结果的区别?原因是啥?

              Condition中的锁为:

              private final Lock lock = new ReentrantLock();

              结果显示:写入和写入互斥,读取和写入互斥,读取和读取互斥

              不能并发执行,效率较低。Condition能够精确唤醒某种条件的线程(注意观察上面运行结果,如果拥有这种条件的线程数就一个,那么就能精确到线程,否则就是其中一个线程(随机唤醒)。),但不能并发执行-即所有线程之间都是互斥的。

                那么在这思考下Condition与Object(wait/notify,notifyAll)有什么区别?

              ReadWriteLock中的锁为:

              private ReadWriteLock rwl = new ReentrantReadWriteLock();


              结果显示:虽然写入和写入互斥了,读取和写入也互斥了,但是读取和读取之间不互斥了,能够并发执行读取数据,相对效率较高,我想现在应该能很好的理解下面这句话了:

              在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetarntLock,但是在很激烈的情况下,synchronized的性能会下降几十倍;


              作者:淡淡的倔强

              来源:blog.csdn.net/article/details/68064253

              版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢

              更多精彩,关注公众号,一起学习、成长

              文章转载自java小哥,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论