Synchronized和ReentrantLock的区别

昨天面试,面试官问了自己一个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进行了很多优化

你可能感兴趣的:(java并发学习)