Synchronized和ReentrantLock的区别

来源:互联网 发布:手机mysql服务器 编辑:程序博客网 时间:2024/06/08 17:49

昨天面试,面试官问了自己一个synchronized和ReentrantLock的区别,感觉自己回答的并不是特别好,今天在翻书学习总结一下,毕竟书读百遍其义自见。

开始进入正题
两者的共同点:
1)协调多线程对共享对象、变量的访问
2)可重入,同一线程可以多次获得同一个锁
3)都保证了可见性和互斥性
两者的不同点:
1)ReentrantLock显示获得、释放锁,synchronized隐式获得释放锁
2)ReentrantLock可响应中断、可轮回,synchronized是不可以响应中断的,为处理锁的不可用性提供了更高的灵活性
3)ReentrantLock是API级别的,synchronized是JVM级别的
4)ReentrantLock可以实现公平锁
5)ReentrantLock通过Condition可以绑定多个条件
6)底层实现不一样, synchronized是同步阻塞,使用的是悲观并发策略,lock是同步非阻塞,采用的是乐观并发策略

虽然ReentrantLock可以提供比synchronized更高级的功能,但是仍不能替换synchronized
为什么呢?
《java并发编程实战》上说是因为如果使用reentrantlock时,你没有释放锁,很难追踪到最初发生错误的位置,因为没有记录应该释放锁的位置和时间。网上找了一下,没有找到其他比较合理的答案,先暂且记住吧

几个方法:
1) boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
time:等待锁定的最长时间
unit: 时间单位
这个方法起到了定时锁的作用,如果在指定时间内没有获取到锁,将会返回false
应用:具有时间限制的操作时使用
一个简单例子:

public class TestReentrantLock {    public static void main(String[] args) {        Lock r = new ReentrantLock();        //线程1        Thread thread1 = new Thread(new Runnable() {            @Override            public void run() {                //获得锁                r.lock();                try {                    System.out.println("线程1获得了锁");                    //睡眠5秒                    Thread.currentThread().sleep(5000);                } catch (InterruptedException e) {                    e.printStackTrace();                } finally {                    r.unlock();                }            }        });        thread1.start();        //线程2        Thread thread2 = new Thread(new Runnable() {            @Override            public void run() {                try {                    if (r.tryLock(1000, TimeUnit.MILLISECONDS)) {                        System.out.println("线程2获得了锁");                    } else {                        System.out.println("获取锁失败了");                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        });        thread2.start();    }}

运行结果:

线程1获得了锁获取锁失败了

此时线程2在1秒之内没有获得到锁

boolean tryLock();
只有在获得锁的情况下才会返回true,可以通过使用这个方法先判断一下能否获得锁,避免长时间获不到锁的等待,以及死锁的产生。这个方法只有一次尝试获得锁的机会。

void lockInterruptibly() throws InterruptedException;
获得可中断锁。可中断锁可以响应中断,可以让它中断自己或者在别的线程中中断它,中断后可以放弃等待,去处理其他事,而不可中断锁不会响应中断,将一直等待,synchronized就是不可中断。
举例子:
使用synchronized关键字

Buffer类:
public class Buffer {
private Object lock;

    public Buffer() {        lock = this;  //buffer自身    }    public void write() {        synchronized (lock) {            long startTime = System.currentTimeMillis();            System.out.println("往这个buffer写入数据...");            //死循环模拟要处理很长时间            for(;;) {                if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) {                    break;                }            }            System.out.println("终于写完了");        }    }    public void read() {        synchronized (lock) {            System.out.println("从这个buff读数据");        }    }    public static void main(String[] args) {        Buffer buffer = new Buffer();        final Writer writer = new Writer(buffer);        final Reader reader = new Reader(buffer);        //启动线程        writer.start();        reader.start();        //尝试启动一个线程去中断reader线程        new Thread(new Runnable() {            @Override            public void run() {                long start = System.currentTimeMillis();                for(;;) {                    //等待五秒钟去中断                    if (System.currentTimeMillis() - start > 5000) {                        System.out.println("不等了,尝试中断");                        reader.interrupt();                        break;                    }                }            }        }).start();    }}

Reader类:

public class Reader extends Thread {    private Buffer buffer;    public Reader(Buffer buffer) {        this.buffer = buffer;    }    public void run() {        buffer.read();  //这里将会引起长时间的阻塞        System.out.println("读结束");    }}

Writer类:

public class Writer extends Thread {    private Buffer buffer;    public Writer(Buffer buffer) {        this.buffer = buffer;    }    public void run() {        buffer.write();    }}

reader类没有被中断

使用ReentrantLock获得中断锁
BufferInterruptibly类:

public class BufferInterruptibly {    private Lock lock = new ReentrantLock();    public void write() {        lock.lock();        try {            long start = System.currentTimeMillis();            System.out.println("开始往这个buff写入数据...");            //模拟要处理很长时间            for (;;) {                if (System.currentTimeMillis() - start > Integer.MAX_VALUE) {                    break;                }            }            System.out.println("终于写完了");        } finally {            lock.unlock();        }    }    public void read() throws InterruptedException {        lock.lockInterruptibly();  //获得可中断锁        try {            System.out.println("从这个buff读数据");        } finally {            lock.unlock();        }    }    public static void main(String args[]) {        BufferInterruptibly buff = new BufferInterruptibly();        final Writer2 writer = new Writer2(buff);        final Reader2 reader = new Reader2(buff);        writer.start();        reader.start();        new Thread(new Runnable() {            @Override            public void run() {                long start = System.currentTimeMillis();                for (;;) {                    if (System.currentTimeMillis()                            - start > 5000) {                        System.out.println("不等了,尝试中断");                        reader.interrupt();  //此处中断读操作                        break;                    }                }            }        }).start();    }}

Reader2类

public class Reader2 extends Thread {    private BufferInterruptibly buff;    public Reader2(BufferInterruptibly buff) {        this.buff = buff;    }    @Override    public void run() {        try {            buff.read();//可以收到中断的异常,从而有效退出        } catch (InterruptedException e) {            System.out.println("我不读了");        }        System.out.println("读结束");    }}

Writere2类:

public class Writer2 extends Thread {    private BufferInterruptibly buff;    public Writer2(BufferInterruptibly buff) {        this.buff = buff;    }    @Override    public void run() {        buff.write();    }}

运行结果:

开始往这个buff写入数据...不等了,尝试中断我不读了读结束

ReentrantLock的公平性
在ReentrantLock中的构造函数中,提供了一个参数,指定是否为公平锁。

public ReentrantLock(boolean fair) {        sync = fair ? new FairSync() : new NonfairSync();    }

如果指定了参数为true,默认为非公平锁。
公平锁:线程将按照它们发出的请求顺序来获得锁
非公锁:当一个线程请求非公平锁的时候,如果发出请求时,获得锁的线程刚好释放锁,则该线程将会获得锁而跳过在该锁上等待的线程。

一个公平锁例子:

public class TestReentrantLock2 {    private static Lock lock = new ReentrantLock(true);  //lock为公平锁    public static void main(String[] args) {        Thread t1 = new Thread(new Runnable() {            @Override            public void run() {                lock.lock();                try {                    System.out.println("线程1启动...");                } finally {                    lock.unlock();                }            }        });        Thread t2 = new Thread(new Runnable() {            @Override            public void run() {                lock.lock();                try {                    System.out.println("线程2启动...");                } finally {                    lock.unlock();                }            }        });        Thread t3 = new Thread(new Runnable() {            @Override            public void run() {                lock.lock();                try {                    System.out.println("线程3启动...");                } finally {                    lock.unlock();                }            }        });        t1.start();        t3.start();        t2.start();    }}

运行结果:

线程1启动...线程3启动...线程2启动...

什么时候选择使用synchronized,什么使用选择使用ReentrantLock
仅当synchronized不能满足时才使用ReentrantLockk,因为使用ReentrantLock要非常小心,不释放锁将影响其他需要该锁的代码块运行
不能使用synchronized不满足的情形:
1)公平性
2)可中断
4)分块结构的加锁,比如jdk1.7ConcurrentHashMap的分段锁(目前还不是提别理解这个,先记住这个例子,后头补充)

synchronized和ReentrantLock两者之间性能的比较
从jdk1.5以后,性能就差不多了,因为jvm对synchronized进行了很多优化

原创粉丝点击