(转)java可重入锁

背景:最近在准备java基础知识,对于可重入锁一直没有个清晰的认识,有必要对这块知识进行总结。

1 . 什么是可重入锁

锁的概念就不用多解释了,当某个线程A已经持有了一个锁,当线程B尝试进入被这个锁保护的代码段的时候.就会被阻塞.

而锁的操作粒度是”线程”,而不是调用(至于为什么要这样,下面解释).同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁,这就是可重入锁
java里面内置锁(synchronize)和Lock(ReentrantLock)都是可重入的

2 . 为什么要可重入

如果线程A继续再次获得这个锁呢?比如一个方法是synchronized,递归调用自己,那么第一

次已经获得了锁,第二次调用的时候还能进入吗? 直观上当然需要能进入.这就要求必须是可重入的.可重入锁又叫做递归锁,再举个例子.

public class Widget {
        public synchronized void doSomething() {
            ...
        }
}
     
public class LoggingWidget extends Widget {
        public synchronized void doSomething() {
            System.out.println(toString() + ": calling doSomething");
            super.doSomething();//若内置锁是不可重入的,则发生死锁
        }
}

 

这个例子是java并发编程实战中的例 子.synchronized 是父类Widget的内置锁,当执行子 类的方法的时候,先获取了一次Widget的锁,然后在执行super的时候,就要获取一次,如果不可重入,那么就跪了.

3 . 如何实现可重入锁

为每个锁关联一个获取计数器和一个所有者线程,当计数值为0的时候,这个所就没有被任何线程持有.

当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,计数值将递增,退出一次同步代码块,计算值递减,当计数值为0时,这个锁就被释放.
ReentrantLock源码里面有实现

ps:可重入是指对同一线程而言。

4 . 有不可重入锁吗

这个还真有.Linux下的pthread_mutex_t锁是默认是非递归的。可以通过设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t锁设置为递归锁。如果要自己实现不可重入锁,同可重入锁,这个计数器只能为1.或者0,再次进入的时候,发现已经是1了,就进行阻塞.jdk里面没有默认的实现类.

5 . demo代码展示

5.1 内置锁的可重入

public class Reentrant {
    public void method1() {
        synchronized (Reentrant.class) {
            System.out.println("method1 run");
            method2();
        }
    }

    public void method2() {
        synchronized (Reentrant.class) {
            System.out.println("method2 run in method1");
        }
    }

    public static void main(String[] args) {
        new Reentrant().method1();
    }
}

 

 

结果:

method1 run
method2 run in method1

 

Lock对象可重入

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Reentrant2 {
    private Lock lock = new ReentrantLock();

    public void method1() {
        lock.lock();
        try {
            System.out.println("method1 run");
            method2();
        } finally {
            lock.unlock();
        }
    }

    public void method2() {
        lock.lock();
        try {
            System.out.println("method2 run in method1");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        new Reentrant2().method1();
    }
}

 

结果:

method1 run
method2 run in method1

 

在同一线程里,method1调用持同样锁的method2,不会等锁。这就是锁的”重入”。

不同线程里锁不可重入

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Reentrant3 {
    private static Lock lock = new ReentrantLock();

    private static class T1 extends Thread {
        @Override
        public void run() {
            System.out.println("Thread 1 start");
            lock.lock();
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            System.out.println("Thread 1 end");
        }
    }

    private static class T2 extends Thread {
        @Override
        public void run() {
            System.out.println("Thread 2 start");
            lock.lock();
            lock.unlock();
            System.out.println("Thread 2 end");
        }
    }


    public static void main(String[] args) {
        new T1().start();
        Thread.sleep(100);
        new T2().start();
    }
}

 

结果:

Thread 1 start
Thread 2 start
Thread 1 end
Thread 2 end

因为不可重入,所以不同线程可以看到T2一定会等到T1释放锁之后。

Synchronized和ReentrantLock关系与区别汇总

(1)synchronized是JVM层面的实现的,JVM会确保释放锁,而且synchronized使用简单;而Lock是个普通类,需要在代码中finally中显式释放锁lock.unlock(),但是使用灵活

(2)synchronized采用的是悲观锁机制,线程获得独占锁,而其他线程只能阻塞来等待释放锁。当竞争激烈时,CPU频繁的上下文切换会降低效率。而Lock是乐观锁机制,每次假设不存在竞争而不上锁,若存在竞争就重试。当竞争激烈时JVM可以花更少的时间来调度线程,把更多时间用在执行线程上,因此性能最佳。

(3)ReentrantLock可以实现定时锁、轮询锁,可以选择放弃等待或者轮询请求。有效防止了死锁。

lock();//用来获取锁,如果锁已被其他线程获取,则进行等待  
tryLock(); //尝试获取锁,若成功返回true,失败(即锁已被其他线程获取)则返回false  
tryLock(long timeout, TimeUnit unit); //在拿不到锁时会等待一定的时间  
//两个线程同时通过lock.lockInterruptibly()想获取某个锁时  
//若线程A获取到了锁,而线程B在等待  
//线程B调用threadB.interrupt()方法能够中断线程B的等待过程  
lockInterruptibly(); 

(4)synchronized是非公平锁。而ReentrantLock可以通过构造函数传入true实现公平锁,即按照申请锁顺序获得锁

(5)ReentrantLock类有一个重要的函数newCondition(),用于获取Lock上的一个条件,Condition可用于线程间通信。

 参考:Java并发——Synchronized和ReentrantLock的联系与区别

 

    原文作者:CS408
    原文地址: https://www.cnblogs.com/lixuwu/p/8596315.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞