美文网首页
ReentrantLock与synchronized的比较

ReentrantLock与synchronized的比较

作者: 文景大大 | 来源:发表于2020-05-01 21:22 被阅读0次

    一、ReentrantLock与synchronized的区别

    首先构造一个线程不安全的例子:

    @Slf4j
    public class Counter {
        private int count = 0;
    
        public int getCount(){
            return count;
        }
    
        public void plus(){
            for (int i = 0; i < 3; i++) {
                log.info("{}的count为:{}", Thread.currentThread().getName(),count++);
            }
        }
    
    }
    
    @Slf4j
    public class MyThread implements Runnable {
        private Counter counter;
    
        public MyThread(Counter counter){
            this.counter = counter;
        }
    
        @Override
        public void run() {
            counter.plus();
            log.info("{}执行完毕counter为:{}", Thread.currentThread().getName(), counter.getCount());
        }
    }
    
    @Slf4j
    public class Test001 {
        public static void main(String[] args) {
            Counter counter = new Counter();
            Thread thread1 = new Thread(new MyThread(counter));
            thread1.start();
            Thread thread2 = new Thread(new MyThread(counter));
            thread2.start();
        }
    }
    

    执行结果的一种可能是:

    Thread-0的count为:0
    Thread-1的count为:1
    Thread-1的count为:3
    Thread-0的count为:2
    Thread-1的count为:4
    Thread-0的count为:5
    Thread-0执行完毕counter为:6
    Thread-1执行完毕counter为:6
    

    不光出现了两个线程交替执行的情况,而且两个线程出现了脏读。

    为了解决线程的并发安全问题,我们可以使用synchronized来解决:

        public void plus(){
            synchronized (this) {
                for (int i = 0; i < 3; i++) {
                    log.info("{}的count为:{}", Thread.currentThread().getName(),count++);
                }
            }
        }
    

    同时, 也可以使用今天的主角ReentrantLock来解决:

    @Slf4j
    public class Counter {
        private Lock lock = new ReentrantLock();
        private int count = 0;
    
        public int getCount(){
            return count;
        }
    
        public void plus(){
            lock.lock();
            for (int i = 0; i < 3; i++) {
                log.info("{}的count为:{}", Thread.currentThread().getName(),count++);
            }
            lock.unlock();
        }
    }
    

    在lock的日常的使用中,Java编码规范建议我们:

    1. 上锁操作后面必须紧跟try代码块,且unlock要放在finally的第一行;
    2. 在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try之间没有任何可能会抛出异常的方法调用,从而避免在加锁后,无法执行finally中的释放锁操作。
    

    所以,如山使用lock的例子应该改为:

        public void plus() {
            lock.lock();
            // lock加锁后紧跟try
            try {
                for (int i = 0; i < 3; i++) {
                    log.info("{}的count为:{}", Thread.currentThread().getName(),count++);
                }
            } catch (Exception e) {
                log.info("{}",e);
            } finally {
                // 始终在finally中释放锁
                lock.unlock();
            }
        }
    

    记录一下ReentrantLock和synchronized的区别:

    • synchronized是Java语言的关键字,其加锁与解锁由JVM实现,比较简洁;而ReentrantLock是API,需要开发者自己配合try-catch-finally来手动实现加锁和解锁,比较繁琐,且容易忘记;
    • synchronized等待无期限设置,而ReentrantLock可以通过tryLock(),tryLock(Long,TimeUnit)的方式尝试获取锁,如果获取不到可以执行其它逻辑;
    • synchronized等待不可被外部中断;而ReentrantLock可以通过lock.lockInterruptibly();的方式实现可被外部中断等待;
    • synchronized是非公平锁;而ReentrantLock可以通过设置new ReentrantLock(true);实现公平锁;

    三、Condition与wait/notify的区别

    在上一篇文章中,我们讲过使用Object的wait和notify/notifyAll可以实现线程间的通信,其实使用condition也同样可以实现。

    @Slf4j
    public class Counter {
        private Lock lock = new ReentrantLock();
        private Condition condition = lock.newCondition();
    
        public void plus1() {
            lock.lock();
            try {
                log.info("线程:{}开始wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
                // 当前线程释放lock锁资源,并在此等待
                condition.await();
                log.info("线程:{}结束wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
            } catch (Exception e) {
                log.info("{}",e);
            } finally {
                lock.unlock();
            }
        }
    
        public void plus2() {
            lock.lock();
            try {
                log.info("线程:{}开始wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
                // 当前线程释放lock锁资源,并在此等待
                condition.await();
                log.info("线程:{}结束wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
            } catch (Exception e) {
                log.info("{}",e);
            } finally {
                lock.unlock();
            }
        }
    
        public void minus(){
            lock.lock();
            try {
                log.info("线程:{}开始signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
                // 唤醒相同condition等待队列中的一个线程
                condition.signal();
                // 唤醒相同condition等待队列中的所有await线程
                //condition.signalAll();
                log.info("线程:{}结束signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
            } catch (Exception e) {
                log.info("{}",e);
            } finally {
                lock.unlock();
            }
        }
    }
    

    除了可以实现Object的wait和notify/notifyAll的功能之外,Condition还可以唤醒指定的线程,只需要使用不同的condition即可。

    @Slf4j
    public class Counter {
        private Lock lock = new ReentrantLock();
        private Condition condition1 = lock.newCondition();
        private Condition condition2 = lock.newCondition();
    
        public void plus1() {
            lock.lock();
            try {
                log.info("线程:{}开始wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
                condition1.await();
                log.info("线程:{}结束wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
            } catch (Exception e) {
                log.info("{}",e);
            } finally {
                lock.unlock();
            }
        }
    
        public void plus2() {
            lock.lock();
            try {
                log.info("线程:{}开始wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
                condition2.await();
                log.info("线程:{}结束wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
            } catch (Exception e) {
                log.info("{}",e);
            } finally {
                lock.unlock();
            }
        }
    
        public void minus(){
            lock.lock();
            try {
                log.info("线程:{}开始condition1的signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
                condition1.signal();
                log.info("线程:{}结束condition1的signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
                Thread.sleep(2000);
                log.info("线程:{}开始condition2的signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
                condition2.signal();
                log.info("线程:{}结束condition2的signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
            } catch (Exception e) {
                log.info("{}",e);
            } finally {
                lock.unlock();
            }
        }
    }
    

    记录下condition与wait/notify的区别:

    • condition需要配合ReentrantLock使用;wait/notify需要配合synchronized使用;
    • condition能够支持唤醒指定的线程;wait/notify不行,只能随机唤醒;
    • condition可以通过await(Long,TimeUnit)来有限等待锁资源;wait/notify不行,必须无限期等待;

    相关文章

      网友评论

          本文标题:ReentrantLock与synchronized的比较

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