1.有了synchronized,为什么还用Lock?Lock的应用场景

  • 解决获取锁的等待问题

如果使用synchronized,线程A要想释放锁,要么线程A执行完毕,要么线程A执行发生异常才能释放锁。

当线程A执行遇到阻塞等情况,线程B要想获取这个锁,必须一直等到线程A释放锁后才能获取锁并执行线程B的程序。

而使用用Lock的tryLock(Long time)方法,可以使线程只等待一定的时间,不会一直等待下去。

  • 读写锁的特别应用

我们知道,在读文件或者读数据库时,是不需要同步等待的,但是写则需要同步等待。

如果用synchronized,则不管读还是写都会同步等待。

而使用Lock的子类ReentrantReadWriteLock,则可实现读不同步,写同步的操作。

2.使用Lock要注意的地方

  • synchronized是java的关键字,是基于JVM层面的;而Lock是java的类,是jdk层面的。
  • synchronized系统会自动释放锁,但Lock不会自动释放,需要手工释放锁,切记切记,否则会造成死锁,只有重启系统了。

3.Lock的方法

public interface Lock {

    //获取锁,没有获取锁,则一直等待,这个和synchronized类似
    void lock();

    //获取可中断的锁
    void lockInterruptibly() throws InterruptedException;

    //获取锁并直接返回结果,true代表获取到锁,false代表未获取到锁
    boolean tryLock();

    //获取锁,一定时间内获取,并返回结果
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    //释放锁
    void unlock();

    //释放等待与唤醒线程,获取的Condition对象实例可以调用await()与signalAll()方法,就相当于synchronized的wait()与notifyAll()方法,这两个方法不明白的可以看我的线程四之线程的方法了解
    Condition newCondition();

4.lock()方法,获取锁,没有获取锁,则一直等待,这个和synchronized类似

看下面的例子:

/**
 * Created by 
 * Date : 2018/7/17 14:52
 */
public class Main {
    private Lock lock=new ReentrantLock();

    public static void main(String[] args){
        final Main m=new Main();
        for(int i=1;i<=3;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    m.getLock();
                }
            }.start();
        }
    }

    public void getLock(){
        lock.lock();
        try{
            System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
            TimeUnit.SECONDS.sleep(2);
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
            lock.unlock();
        }
    }
}

执行结果:

线程1获取锁
线程1释放锁
线程2获取锁
线程2释放锁
线程3获取锁
线程3释放锁

其中要注意:private Lock lock=new ReentrantLock();这个不能放在方法中,如果放在方法中,每个线程则各自都会有自己独立的锁,就不会互斥了。

放在方法中的代码如下:

/**
 * Created by 
 * Date : 2018/7/17 14:52
 */
public class Main {
    public static void main(String[] args){
        final Main m=new Main();
        for(int i=1;i<=3;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    m.getLock();
                }
            }.start();
        }
    }

    public void getLock(){
        Lock lock=new ReentrantLock();
        lock.lock();
        try{
            System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
            TimeUnit.SECONDS.sleep(2);
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
            lock.unlock();
        }
    }
}

执行结果:

线程1获取锁
线程2获取锁
线程3获取锁
线程1释放锁
线程2释放锁
线程3释放锁

还有一个要注意的是:lock.lock()方法不能放在try中,因为如果是lock.lock()本身的方法抛异常,则跳到catch中,再跳到finally中,执行释放锁,还会抛出异常。

5.tryLock()方法,无论是否获取到锁,都会返回结果,不进行等待,其中tryLock(long time,TimeUnit timeUnit)方法是在一定时间内获取

举例如下:

/**
 * Created by 
 * Date : 2018/7/17 14:52
 */
public class Main {
    private Lock lock=new ReentrantLock();
    public static void main(String[] args){
        final Main m=new Main();
        for(int i=1;i<=3;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    m.getTryLock();
                }
            }.start();
        }
    }

    public void getTryLock(){
        if(lock.tryLock()){
            try{
                System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
                TimeUnit.SECONDS.sleep(2);
            }catch (Exception e){
                e.printStackTrace();
            }finally{
                System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
                lock.unlock();
            }
        }else{
            System.out.println("线程"+Thread.currentThread().getName()+"未获取到锁");
        }
    }
}

执行结果:

线程1获取锁
线程2未获取到锁
线程3未获取到锁
线程1释放锁

从以上结果就可以看出,线程2和3获取不到锁就直接返回false。

再看看tryLock(long time,TimeUnit timeUnit)的使用方法:

/**
 * Created by 
 * Date : 2018/7/17 14:52
 */
public class Main {
    private Lock lock=new ReentrantLock();
    public static void main(String[] args){
        final Main m=new Main();
        for(int i=1;i<=3;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    try{
                        m.getTryLock();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }

    public void getTryLock()throws InterruptedException{
        if(lock.tryLock(2,TimeUnit.SECONDS)){
            try{
                System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
                TimeUnit.SECONDS.sleep(2);
            }catch (Exception e){
                e.printStackTrace();
            }finally{
                System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
                lock.unlock();
            }
        }else{
            System.out.println("线程"+Thread.currentThread().getName()+"未获取到锁");
        }
    }
}

执行结果:

线程2获取锁
线程2释放锁
线程3获取锁
线程1未获取到锁
线程3释放锁

我们通过结果看到,tryLock方法等待了两秒后,线程3获取到了锁,但线程1未获取到就返回了。

6.lockInterruptibly(),讲到这个方法时,我要吐槽一下,百度上查询这个方法,查出来的都是千篇一律的lock()与lockInterruptibly()的区别,好多文章都是一模一样的,讲的都是不明不白,没经过实践,真不知道直接复制过来有啥用。

用这个方法最主要是方便中断线程,但中断是有条件的,必须是阻塞情况下或者用sleep()等情况下调用interrupt()方法才会中断,否则不会中断,继续执行。其中要注意的是中断是使用线程的interrupt()方法。看下面的例子:

/**
 * Created by 
 * Date : 2018/7/17 14:52
 */
public class Main {
    private Lock lock=new ReentrantLock();
    public static void main(String[] args)throws InterruptedException{
        final Main m=new Main();
        Thread t1=new Thread("t1"){
            public void run(){
                try{
                    m.getInterruptLock();
                }catch (InterruptedException e){
                    System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
                }
            }
        };

        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();
    }

    public void getInterruptLock()throws InterruptedException{
        lock.lockInterruptibly();
        try{
            System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
            //耗时操作
            long startTime = System.currentTimeMillis();
            for(    ;     ; ) {
                if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) {
                    break;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
            lock.unlock();
        }
    }
}

执行结果:

线程t1获取锁

这个程序在一直执行,直到循环结束释放锁。虽然调用了t1.interrupt();方法,但是线程并没有被中断,还在继续执行中。

如果我们将耗时操作换为sleep()方法,那么这个程序就会被中断,示例如下:

/**
 * Created by 
 * Date : 2018/7/17 14:52
 */
public class Main {
    private Lock lock=new ReentrantLock();
    public static void main(String[] args)throws InterruptedException{
        final Main m=new Main();
        Thread t1=new Thread("t1"){
            public void run(){
                try{
                    m.getInterruptLock();
                }catch (InterruptedException e){
                    System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
                }
            }
        };

        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t1.interrupt();
    }

    public void getInterruptLock()throws InterruptedException{
        lock.lockInterruptibly();
        try{
            System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
            //耗时操作
            TimeUnit.SECONDS.sleep(5);
        }catch (Exception e){
            System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
            e.printStackTrace();
        }finally{
            System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
            lock.unlock();
        }
    }
}

执行结果:

线程t1获取锁
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:360)
    at threadTest.lock.Main.getInterruptLock(Main.java:35)
    at threadTest.lock.Main$1.run(Main.java:18)
线程t1释放锁

从结果来看,因为调用了sleep被中断了,所以打印了中断异常,注意,这儿的异常是被getInterruptLock()方法中的try-catch捕获的。

我们再来看看等待锁被中断的情况:

/**
 * Created by WangZhiXin
 * Date : 2018/7/17 14:52
 */
public class Main {
    private Lock lock=new ReentrantLock();
    public static void main(String[] args)throws InterruptedException{
        final Main m=new Main();
        Thread t1=new Thread("t1"){
            public void run(){
                try{
                    m.getInterruptLock();
                }catch (InterruptedException e){
                    System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
                }
            }
        };

        Thread t2=new Thread("t2"){
            public void run(){
                try{
                    m.getInterruptLock();
                }catch (InterruptedException e){
                    System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
                    e.printStackTrace();
                }
            }
        };

        t1.start();
        TimeUnit.SECONDS.sleep(1);
        t2.start();
        TimeUnit.SECONDS.sleep(2);
        t2.interrupt();
    }

    public void getInterruptLock()throws InterruptedException{
        lock.lockInterruptibly();
        try{
            System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
            //耗时操作
            long startTime = System.currentTimeMillis();
            for(    ;     ; ) {
                if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) {
                    break;
                }
            }
        }catch (Exception e){
            System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
            e.printStackTrace();
        }finally{
            System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
            lock.unlock();
        }
    }
}

执行结果:

线程t1获取锁
线程t2中断锁
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
    at threadTest.lock.Main.getInterruptLock(Main.java:44)
    at threadTest.lock.Main$2.run(Main.java:28)

从结果来看,线程t1获取了锁,t2则一直等待t1释放锁,但是在等待的过程中被中断了,便抛出了中断异常。

网上还有一个坑人的地方,就是网上好多示例把lock.lockInterrupt()方法放在了try-catch中,这个其实做法很不规范,看下面的例子:

/**
 * Created by 
 * Date : 2018/7/17 14:52
 */
public class Main {
    private Lock lock=new ReentrantLock();
    public static void main(String[] args)throws InterruptedException{
        final Main m=new Main();
        Thread t1=new Thread("t1"){
            public void run(){
                try{
                    m.getInterruptLock();
                }catch (InterruptedException e){
                    System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
                }
            }
        };

        Thread t2=new Thread("t2"){
            public void run(){
                try{
                    m.getInterruptLock();
                }catch (InterruptedException e){
                    System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
                }
            }
        };

        t1.start();
        TimeUnit.SECONDS.sleep(1);
        t2.start();
        TimeUnit.SECONDS.sleep(2);
        t2.interrupt();
    }

    public void getInterruptLock()throws InterruptedException{
        try{
            lock.lockInterruptibly();
            System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
            //耗时操作
            long startTime = System.currentTimeMillis();
            for(    ;     ; ) {
                if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) {
                    break;
                }
            }
        }catch (Exception e){
            System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
        }finally{
            System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
            lock.unlock();
        }
    }
}

执行结果:

线程t1获取锁
线程t2中断锁
线程t2释放锁
Exception in thread "t2" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
    at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
    at threadTest.lock.Main.getInterruptLock(Main.java:57)
    at threadTest.lock.Main$2.run(Main.java:28)

从结果看到没,抛出了IllegalMonitorStateException异常,而不是中断异常,为什么会抛出这个异常?是因为线程t2在调用lock.lockInterruptibly()时,锁一直被t1持有,所以一直在等待锁,而这时候t2又中断了线程,所以就会抛出中断的异常,但是最后有个finally执行lock.unlock();因为t2并未获取到锁,这儿却又调用了释放锁,所以抛出了IllegalMonitorStateException异常。

7.ReentrantReadWriteLock,这个类是用于读写的类,读异步,写同步,直接看例子:

/**
 * Created by WangZhiXin
 * Date : 2018/7/18 16:46
 */
public class ReadAndWrite {
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    public static void main(String[] args){
        final ReadAndWrite rw=new ReadAndWrite();
        for(int i=1;i<=10;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    rw.read();
                }
            }.start();
        }

        for(int i=11;i<=12;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    rw.write();
                }
            }.start();
        }
    }

    public void read(){
        rwLock.readLock().lock(); // 在外面获取锁
        try {
            long start = System.currentTimeMillis();
            System.out.println("线程" + Thread.currentThread().getName() + "开始读操作...");
            while (System.currentTimeMillis() - start <= 1) {

            }
            System.out.println("线程" + Thread.currentThread().getName() + "读操作完毕...");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }
    }
    public void write(){
        rwLock.writeLock().lock(); // 在外面获取锁
        try {
            long start = System.currentTimeMillis();
            System.out.println("线程" + Thread.currentThread().getName() + "开始写操作...");
            while (System.currentTimeMillis() - start <= 1) {

            }
            System.out.println("线程" + Thread.currentThread().getName() + "写操作完毕...");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

执行结果:

线程1开始读操作...
线程1读操作完毕...
线程3开始读操作...
线程4开始读操作...
线程4读操作完毕...
线程5开始读操作...
线程3读操作完毕...
线程7开始读操作...
线程7读操作完毕...
线程5读操作完毕...
线程9开始读操作...
线程9读操作完毕...
线程11开始写操作...
线程11写操作完毕...
线程8开始读操作...
线程8读操作完毕...
线程12开始写操作...
线程12写操作完毕...
线程2开始读操作...
线程2读操作完毕...
线程6开始读操作...
线程6读操作完毕...
线程10开始读操作...
线程10读操作完毕...

从结果可以看出,有的读操作还没读完,就已经执行了另一个读操作;但是写操作是必须执行完才会再执行其它的读和写操作。

8.公平锁与非公平锁

故名思义:

公平锁就是按执行顺序获取锁,用new ReentrantLock(true)表示。

非公平锁就是无序的竞争锁,用new ReentrantLock(false)或new ReentrantLock()表示。

直接上代码:

/**
 * Created by WangZhiXin
 * Date : 2018/7/17 14:52
 */
public class Main {
    private Lock lock=new ReentrantLock(true);
    public static void main(String[] args)throws InterruptedException{
        final Main m=new Main();
        for(int i=1;i<10;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    System.out.println("线程"+Thread.currentThread().getName()+"执行");
                    m.test();
                }
            }.start();
        }
    }
    public void test(){
        lock.lock();
        try{
            System.out.println("线程"+Thread.currentThread().getName()+"拿到锁");
        }catch (Exception e){
            System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
        }finally{
            lock.unlock();
        }
    }
}

执行结果:

线程4执行
线程9执行
线程4拿到锁
线程9拿到锁
线程5执行
线程5拿到锁
线程1执行
线程1拿到锁
线程8执行
线程8拿到锁
线程2执行
线程2拿到锁
线程6执行
线程6拿到锁
线程3执行
线程3拿到锁
线程7执行
线程7拿到锁

从结果看到没,都是按照线程的执行顺序获得锁的。谁先执行,谁就先拿到锁。

再看看非公平锁的情况:仅仅是将private Lock lock=new ReentrantLock(true);换成private Lock lock=new ReentrantLock();

执行结果为:

线程2执行
线程2拿到锁
线程3执行
线程3拿到锁
线程1执行
线程1拿到锁
线程7执行
线程7拿到锁
线程8执行
线程4执行
线程8拿到锁
线程9执行
线程9拿到锁
线程5执行
线程6执行
线程5拿到锁
线程4拿到锁
线程6拿到锁

看看结果,尤其是看线程4的结果,线程4在线程9之前执行的,但是线程4却在线程9之后才获取锁。

9.newCondition(),其实这个往简单了看,你就当它和synchronized的wait()与notifyAll()一样就行

看下面的例子:

/**
 * Created by 
 * Date : 2018/7/19 9:32
 */
public class LockCondition {
    private Lock lock=new ReentrantLock();//创建lock锁
    private Condition newCondition = lock.newCondition();//创建条件对象
    public static void main(String[] args)throws InterruptedException{
        LockCondition lc=new LockCondition();
        lc.await();

        TimeUnit.SECONDS.sleep(2);

        System.out.println("开始唤醒线程");
        lc.testNotify();
    }
    public void await(){
        for(int i=1;i<=5;i++){
            new Thread(String.valueOf(i)){
                public void run(){
                    lock.lock();
                    System.out.println("线程"+Thread.currentThread().getName()+"执行线程");
                    try{
                        newCondition.await();//释放锁并等待
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }finally {
                        System.out.println("线程"+Thread.currentThread().getName()+"结束线程");
                        lock.unlock();
                    }
                }
            }.start();
        }
    }

    public void testNotify(){
        lock.lock();
        try{
            newCondition.signalAll();//唤醒锁
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

执行结果:

线程1执行线程
线程2执行线程
线程3执行线程
线程4执行线程
线程5执行线程
开始唤醒线程
线程1结束线程
线程2结束线程
线程3结束线程
线程4结束线程
线程5结束线程

从结果上来看,是不是和wait()与notifyAll()是一样的道理。

如果当newCondition.signalAll()改为newCondition.signal(),则和notify()是一样的,会随机取一个线程唤醒,执行结果如下:

线程1执行线程
线程2执行线程
线程5执行线程
线程4执行线程
线程3执行线程
开始唤醒线程
线程1结束线程