美文网首页
多线层开发艺术之同步锁对比区别

多线层开发艺术之同步锁对比区别

作者: 哥哥是欧巴Vitory | 来源:发表于2018-12-27 08:40 被阅读0次

    synchronized和volatile区别

    synochronizd和volatile关键字区别:

    1. volatile关键字解决的是变量在多个线程之间的可见性;而sychronized关键字解决的是多个线程之间访问共享资源的同步性。

    2. volatile只能用于修饰变量,而synchronized可以修饰方法,以及代码块。(volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好,并且随着JDK新版本的发布,sychronized关键字在执行上得到很大的提升,在开发中使用synchronized关键字的比率还是比较大)

    3. 多线程访问volatile不会发生阻塞,而sychronized会出现阻塞。

    4. volatile能保证变量在多个线程之间的可见性,但不能保证原子性;而sychronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。

    线程安全包含原子性和可见性两个方面。

    对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的。

    一句话说明volatile的作用:实现变量在多个线程之间的可见性。

    synchronized和lock区别

    1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

    2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

    3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

    4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

    5)Lock可以提高多个线程进行读操作的效率(读写锁)。

      在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

    锁的类型

    1.可重入锁

    如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

    class MyClass {

        public synchronized void method1() {

            method2();

        }

        public synchronized void method2() {

        }

    }

    上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。

    而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

    2.可中断锁

    可中断锁:顾名思义,就是可以相应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。

    如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。lockInterruptibly()方法。

    3.公平锁

    公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。

    非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

    在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。

    而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

    我们可以在创建ReentrantLock对象时,通过以下方式来设置锁的公平性:

    ReentrantLock lock = new ReentrantLock(true);

    如果参数为true表示为公平锁,为fasle为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。

    另外在ReentrantLock类中定义了很多方法,比如:

       isFair()        //判断锁是否是公平锁

      isLocked()    //判断锁是否被任何线程获取了

      isHeldByCurrentThread()  //判断锁是否被当前线程获取了

      hasQueuedThreads()  //判断是否有线程在等待该锁

    在ReentrantReadWriteLock中也有类似的方法,同样也可以设置为公平锁和非公平锁。不过要记住,ReentrantReadWriteLock并未实现Lock接口,它实现的是ReadWriteLock接口。

    4.读写锁

    读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。

    正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。

    ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。

    线程间通信Condition

    Condition可以替代传统的线程间通信,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。

    ——为什么方法名不直接叫wait()/notify()/nofityAll()?因为Object的这几个方法是final的,不可重写!

    传统线程的通信方式,Condition都可以实现。

    注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。

    Condition的强大之处在于它可以为多个线程间建立不同的Condition

    看JDK文档中的一个例子:假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition 实例来做到这一点。

    ——其实就是java.util.concurrent.ArrayBlockingQueue的功能

    class BoundedBuffer {

      final Lock lock = new ReentrantLock();          //锁对象

      final Condition notFull  = lock.newCondition(); //写线程锁

      final Condition notEmpty = lock.newCondition(); //读线程锁

      final Object[] items = new Object[100];//缓存队列

      int putptr;  //写索引

      int takeptr; //读索引

      int count;  //队列中数据数目

      //写

      public void put(Object x) throws InterruptedException {

        lock.lock(); //锁定

        try {

          // 如果队列满,则阻塞<写线程>

          while (count == items.length) {

            notFull.await();

          }

          // 写入队列,并更新写索引

          items[putptr] = x;

          if (++putptr == items.length) putptr = 0;

          ++count;

          // 唤醒<读线程>

          notEmpty.signal();

        } finally {

          lock.unlock();//解除锁定

        }

      }

      //读

      public Object take() throws InterruptedException {

        lock.lock(); //锁定

        try {

          // 如果队列空,则阻塞<读线程>

          while (count == 0) {

              notEmpty.await();

          }

          //读取队列,并更新读索引

          Object x = items[takeptr];

          if (++takeptr == items.length) takeptr = 0;

          --count;

          // 唤醒<写线程>

          notFull.signal();

          return x;

        } finally {

          lock.unlock();//解除锁定

        }

      }

    }

    优点:

    假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程。

    那么假设只有一个Condition会有什么效果呢?缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这时又去唤醒,这样就浪费了很多时间。

    相关文章

      网友评论

          本文标题:多线层开发艺术之同步锁对比区别

          本文链接:https://www.haomeiwen.com/subject/mtfflqtx.html