锁 - 可重入 vs 不可重入

2022-11-21  本文已影响0人  面向对象架构

可重入锁

在多线程编程和信号处理过程中,经常会遇到可重入(reentrance)线程安全(thread-safe)

什么是重入

函数在执行时,由于外部原因或内部调用,又一次进入该函数执行。

重入一般发生在:

  1. 多个线程同时执行该函数
  2. 函数自身调用自身

什么是可重入函数

可重入的英文关键词是 reentrant 或 re-entrant

解释:运行某个函数或者代码时由于某个原因(中断或者抢占资源问题)而中止函数或代码的运行,等到问题解决后,重新进入该函数或者代码继续运行,其结果不受影响。那么这个函数或者代码就称为可重入的。简而言之,在异步环境下,可以重新进入。

只要函数中的数据能被多个进程或者线程共享,那么这个函数就一定不是可重入函数。

不可重入会带来不能预见的结果,尤其在内核中,如果操作不当会让系统直接挂掉。所以内核代码一定要是可重入的。

可重入的函数的特点(不能有可被其他函数或代码所共享的数据):

  1. 不使用任何静态或全局的非常量对象
  2. 不返回任何静态或全局的非常量对象的指针或地址
  3. 只依赖于或只能处理调用方提供的参数/数据
  4. 不依赖任何单个资源的锁
  5. 不调用任何不可重入的函数

可重入与线程安全的关系

一般而言,可重入的函数一定是线程安全的,反之则不一定成立。在不加锁的前提下,如果一个函数用到了全局或静态变量,那么它不是线程安全的,也不是可重入的。如果我们加以改进,对全局变量的访问加锁,此时它是线程安全的但不是可重入的,因为通常的枷锁方式是针对不同线程的访问(如Java的Synchronized),当同一个线程多次访问就会出现问题。


01-technology_B-advanced_B02-Function-Diff-In-ThreadSafe-And-Reentrant.png

与线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然是安全的。

两者的区别:

(1)线程安全不一定是可重入的,而可重入函数一定是线程安全的。

(2)线程安全是多个线程下引起的,但可重入函数可以在只有一个线程的情况下发生。

(3)若一个函数中存在全局变量,那么这个函数既不是线程安全的也不是可重入的。

(4)线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响结果是相同的。

可重入锁

典型的可重入锁:ReentrantLock、synchronized、Lock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock

可重入锁,也叫递归锁,指的是线程可以进入任何一个它已经拥有锁的所有同步代码块。

可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不会发生死锁(前提是同一个对象或者class)。

public class Test implements Runnable {
     
        public synchronized void get(){
            System.out.println(Thread.currentThread().getId());
            set();
        }
     
        public synchronized void set(){
            System.out.println(Thread.currentThread().getId());
        }
     
        @Override
        public void run() {
            get();
        }
        public static void main(String[] args) {
            Test ss=new Test();
            new Thread(ss).start();
            new Thread(ss).start();
            new Thread(ss).start();
        }
    }
     
    public class Test implements Runnable {
        ReentrantLock lock = new ReentrantLock();
     
        public void get() {
            lock.lock();
            System.out.println(Thread.currentThread().getId());
            set();
            lock.unlock();
        }
     
        public void set() {
            lock.lock();
            System.out.println(Thread.currentThread().getId());
            lock.unlock();
        }
     
        @Override
        public void run() {
            get();
        }
     
        public static void main(String[] args) {
            Test ss = new Test();
            new Thread(ss).start();
            new Thread(ss).start();
            new Thread(ss).start();
        }
    }

同一个线程id被连续输出两次。
结果如下:
Threadid: 8
Threadid: 8
Threadid: 10
Threadid: 10
Threadid: 9
Threadid: 9

不可重入锁

不可重入锁是指:与重入锁相反,不可递归调用,递归调用就发生死锁

// 自旋实现的不可重入锁
public class Lock {
     private boolean isLocked = false;
     public synchronized void lock() throws InterruptedException{
         while(isLocked){    
             wait();
         }
         isLocked = true;
     }
     public synchronized void unlock(){
         isLocked = false;
         notify();
     }
}

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {
    private AtomicReference owner = new AtomicReference();
    public void lock() {
        Thread current = Thread.currentThread();
        //这句是很经典的“自旋”语法,AtomicInteger中也有
        for(;;) {
            if(!owner.compareAndSet(null, current)) {
                return;
            }
        }
    }

    public void unlock(){
        Thread current = Thread.currentThread();
        owner.compareAndSet(current,null);
    }
}
上一篇 下一篇

猜你喜欢

热点阅读