其实在聊线程的锁(synchronized和lock)现在再补充一些东西,比如可重入锁,公平锁。

可重入锁

其实synchronized(隐式)和Lock(显示)两中锁其实都是可重入。

其实这个听着很神奇,用一个简单的例子来说就是,比如如果一座大楼锁门了,然后在里面的人还是可以随便在公司中打开公司大门的锁的,而公司门锁了里面的人自然可以在房间里面随便打开锁然后进去。说白了这个就是一个”套锁“的现象所以可重入锁又被称为递归锁

java lock为什么是可重入锁 java可重入锁有哪些_递归锁

其实这个说白了就是,你锁住了最外面一层,里面所以加锁等操作了。

还是老规矩演示:

public class test {

    public static void main(String[] args) {
//        前面聊过锁到底锁什么,所以就随便弄一个锁的对象,没有什么具体意义
        Object obj = new Object();
//        这里用lambda表达式,如果不懂可以翻看一下前面我的文章
        new Thread(() -> {
            synchronized (obj) {
                System.out.println("写字楼大门锁");

                synchronized (obj) {
                    System.out.println("公司大门锁");
                    synchronized (obj) {
                        System.out.println("房间大门锁");

                    }
                }

            }
        }).start();

    }
}

看一下结果

java lock为什么是可重入锁 java可重入锁有哪些_java lock为什么是可重入锁_02

这个可重入锁使用synchronized还好会自动释放锁,但是使用lock的话会有一个一些要注意的点。不过还是老规矩,用lock在重现上面的代码一次。

public class test {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
//        这里用lambda表达式,如果不懂可以翻看一下前面我的文章
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("写字楼大门锁");

                lock.lock();
                try {
                    System.out.println("公司大门锁");
                    lock.lock();
                    try {
                        System.out.println("房间大门锁");

                    } finally {
                        lock.unlock();
                    }
                } finally {
                    lock.unlock();
                }

            } finally {
                lock.unlock();
            }
        }).start();

    }
}

java lock为什么是可重入锁 java可重入锁有哪些_递归锁_03

结果一样,但是如果这样做呢,将其中的一个锁锁了然后忘了关闭,又如何?

如果最里面的一层忘了解锁(无论如何同一个lock在锁了多少次一般要解锁多少次,不然会出现错误)

演示这个:

public class test {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
//        这里用lambda表达式,如果不懂可以翻看一下前面我的文章
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("写字楼大门锁");

                lock.lock();
                try {
                    System.out.println("公司大门锁");
                    lock.lock();
                    try {
                        System.out.println("房间大门锁");

                    } finally {
//                        如果最里面的一层忘了解锁
//                        lock.unlock();
                    }
                } finally {
                    lock.unlock();
                }

            } finally {
                lock.unlock();
            }
        }).start();
// 再次来一个线程:
        new Thread(()->{
            lock.lock();
            try{
            System.out.println("第二个次要进入");
            }finally {
                lock.unlock();
            }
        }).start();



    }
}

java lock为什么是可重入锁 java可重入锁有哪些_可重入锁_04

会发现无法运行第二个线程,以及程序一直处于运行状态。所以在使用lock加锁解锁的时候一定要成对出现,不然浪费性能以外还会影响其它程序的运行。

公平锁和非公平锁

公平锁和非公平锁,这个其实很好理解的,其实最重要的词语在于公平,毕竟多线程说白就是抢资源。还是老规矩,用例子来测试。

非公平锁:

  • 先用synchronized试一下:
public class test implements  Runnable {
    static Integer number=30;
    public static void main(String[] args) {
        for (int i = 0; i < 30; i++) {
            new Thread(new test(),"AA").start();
        }
        for (int i = 0; i < 30; i++) {
            new Thread(new test(),"BB").start();
        }
    }


    @Override
    public  void run() {
       synchronized (number){
         if(number>0){

             System.out.println("线程名:"+Thread.currentThread().getName()+"卖了票"+(number--)+"还剩下票数"+number);
         }}
    }
}

java lock为什么是可重入锁 java可重入锁有哪些_递归锁_05

可以看出一个事情,那就是所有的票都被AA卖完了。其实聊公平和非公平锁与synchronized关系不太大,因为synchronized的锁很多时候程序员很难操作的。

  • Lock

现在用lock,可以如下看到:

Lock lock= new ReentrantLock();

通过这个构造方法可以查看:

java lock为什么是可重入锁 java可重入锁有哪些_递归锁_06

会发现再创建Lock的时候,会有两个方法,一个公平同步,一个是非公平。不带参数的为非公平锁,所以如下尝试:

public class test implements  Runnable {
    static Integer number=30;
    Lock lock= new ReentrantLock();
    public static void main(String[] args) {

        for (int i = 0; i < 30; i++) {
            new Thread(new test(),"AA").start();
        }
        for (int i = 0; i < 30; i++) {
            new Thread(new test(),"BB").start();
        }
    }


    @Override
    public  void run() {
        lock.lock();
        try{
         if(number>0){

             System.out.println("线程名:"+Thread.currentThread().getName()+"卖了票"+(number--)+"还剩下票数"+number);
         }}finally {
            lock.unlock();
        }
    }
}

java lock为什么是可重入锁 java可重入锁有哪些_java lock为什么是可重入锁_07

两种方式都可以看出一件事情,那就是所有的票都被AA卖完了。

公平锁

上面说过lock在创建锁的时候可以带有参数,所以如下:

public class test implements  Runnable {
    static Integer number=30;
    Lock lock= new ReentrantLock(true);
    public static void main(String[] args) {

        for (int i = 0; i < 30; i++) {
            new Thread(new test(),"AA").start();
        }
        for (int i = 0; i < 30; i++) {
            new Thread(new test(),"BB").start();
        }
    }


    @Override
    public  void run() {
        lock.lock();
        try{
         if(number>0){

             System.out.println("线程名:"+Thread.currentThread().getName()+"卖了票"+(number--)+"还剩下票数"+number);
         }}finally {
            lock.unlock();
        }
    }
}

但是公平锁只能说是相对式公平,而不是绝对公平,看结果:

java lock为什么是可重入锁 java可重入锁有哪些_java_08

可以看出BB虽然也参与了卖票,但是也不是说与AA线程的平分所有卖票数据。

总结:

  • 非公平锁:
    其效率会高,毕竟疯狂的抢占资源,但是其有一个问题那就是有些线程会被饿死(也就是运行无效)
  • 公平锁:
    效率相对较低,其运行的时候只是打个招呼说有没有在等,如果人等着运行自己就开始运行,所以公平也不是绝对的公平运行所有数据。