ReentrantLock与synchronized的比较

2020-05-01  本文已影响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的区别:

三、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的区别:

上一篇下一篇

猜你喜欢

热点阅读