【并发编程】 --- synchronized/ReentrantLock两大特性(可重入性和不可中断性)介绍

源码地址:https://github.com/nieandsun/concurrent-study.git


1 可重入特性


1.1 可重入的含义及synchronized可重入特性演示

可重入的含义:指的是同一个线程获得锁之后,再不释放锁的情况下,可以直接再次获取到该锁。

  • synchronized为可重入锁验证demo:
package com.nrsc.ch1.base.jmm.syn_study.texing;

import lombok.extern.slf4j.Slf4j;

/***
 * 可重入特性: 指的是同一个线程获得锁之后,可以直接再次获取该锁
 * 最常出现的场景: 递归
 * synchronized为可重入锁验证demo
 */
@Slf4j
public class SynReentrantDemo {

    public static void main(String[] args) {

        Runnable sellTicket = new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    String name = Thread.currentThread().getName();
                    log.info("我是run,抢到锁的是{}", name);
                    test01();
                } //正常来说走出临界区(这个括号)才会释放锁,但是再没走出之前,又进入test01,
                //而test01需要和本方法一样的锁
                //如果不可重入的话,就将出现死锁了-->即test01方法等着释放锁,而run方法又不会释放锁
                //因此synchronized只有可以在不释放run方法的锁的情况下,又再次获得该锁才不会有问题
            }

            public void test01() {
                synchronized (this) {
                    String name = Thread.currentThread().getName();
                    log.info("我是test01,抢到锁的是{}", name);
                }
            }
        };
        new Thread(sellTicket).start();
        new Thread(sellTicket).start();
    }
}
  • 运行结果:

在这里插入图片描述


1.2 简单说一下synchronized可重入原理

首先应该知道synchronized锁的并不是同步代码块,而是锁对象关联的一个monitor对象(在java中每一个对象都会关联一个monitor对象),而这个对象里有一个变量叫recursions — 中文是递归的意思(我想大概是因为递归的时候发生可重入的几率应该是最大的,所以才用这个当变量名的吧),其实可以将它简单理解为一个计数器。

以上面的栗子为例:

  • (1)当线程1抢到run方法的执行权即抢到锁时,这个recursions的值就变为了1;
  • (2)线程1接着运行并进入test01方法后,发现还是线程1且还是要this这把锁,就将recursions的值再+1;
  • (3)当线程1,执行完test01方法时,recursions的值又-1
  • (4)执行完run方法时recursions的值又-1,就变为了0,也就是表示线程1已经释放了this锁。
  • (5)之后其他线程就可以继续抢this锁了。

当然ReentrantLock肯定也是可重入的,人家的名字翻译过来就是可重入锁☺☺☺ ,这里就不贴代码了。


2 synchronized不可中断特性 — interrupt和stop都不可中断


2.1 不可中断的含义及synchronized不可中断特性演示

不可中断的含义: 第一个线程获得某把锁后,第二个线程也想要获得该锁,则它必须处于阻塞或等待状态。如果第一个线程不释放锁,那第二个线程就会一直阻塞或等待,不可被中断。

  • synchronized不可中断性验证
package com.nrsc.ch1.base.jmm.syn_study.texing;

import lombok.extern.slf4j.Slf4j;

/***
 *  演示synchronized不可中断的思路    
 *   1.定义一个Runnable        
 *   2.在Runnable定义同步代码块        
 *   3.先开启一个线程来执行同步代码块,保证不退出同步代码块        
 *   4.后开启一个线程来执行同步代码块(阻塞状态)        
 *   5.停止第二个线程  --- 可以发现无法停止
 */
@Slf4j
public class UninterruptibleDemo {
    private static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 1. 定义一个Runnable
        Runnable run = () -> {
            // 2.在Runnable定义同步代码块
            synchronized (obj) {
                try {
                    String name = Thread.currentThread().getName();
                    log.info("线程{}进入同步代码块", name);
                    Thread.sleep(888888);  //保证不退出同步代码块
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        // 3. 先开启一个线程来执行同步代码块
        Thread t1 = new Thread(run);
        t1.start();
        Thread.sleep(1000);
        
        // 4. 后开启一个线程来执行同步代码块(阻塞状态)
        Thread t2 = new Thread(run);
        t2.start();
        
        // 5.停止第二个线程
        log.info("线程t2尝试停止线程前");
        t2.interrupt();
        //t2.stop(); //无论使用interrupt还是stop都不能把t2给中断
        log.info("线程t2尝试停止线程后");
        
        
        log.info("线程t1的状态:{}", t1.getState());
        log.info("线程t2的状态:{}", t2.getState());
    }
}
  • 测试结果

在这里插入图片描述


2.2 ReentrantLock的可中断与不可中断

有没有感觉synchronized的不可中断机制其实是有一定问题的。比如说有一个线程抢了很久很久发现一直就是抢不到执行权,竟然还不能让它知难而退了 —> 这感觉像极了追女朋友,我追了很久很久很久,就是追不到,但是还不准许我不追了。。

—》 我想也许Doug Lea正是发现了这一点,所以在设计ReentrantLock时搞了个tryLock方法。

2.2.1 ReentrantLock使用lock()加锁 — interrupt不可中断,stop可中断

  • 将2.1中的代码修改成使用ReentrantLock加锁:
package com.nrsc.ch1.base.jmm.syn_study.texing;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class ReentrantLockUninterruptibleDemo {
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        // 1. 定义一个Runnable
        Runnable run = () -> {
            // 2.对方法进行加锁
            lock.lock();
            try {
                String name = Thread.currentThread().getName();
                log.info("线程{}进入同步代码块", name);
                Thread.sleep(888888);  //保证不退出同步代码块
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        };

        // 3. 先开启一个线程来执行同步代码块
        Thread t1 = new Thread(run);
        t1.start();
        Thread.sleep(1000);

        // 4. 后开启一个线程来执行同步代码块(阻塞状态)
        Thread t2 = new Thread(run);
        t2.start();

        // 5.停止第二个线程
        log.info("线程t2尝试停止线程前");
        t2.interrupt(); //使用interrupt不可中断
        //t2.stop(); //使用stop可中断
        log.info("线程t2尝试停止线程后");


        log.info("线程t1的状态:{}", t1.getState());
        log.info("线程t2的状态:{}", t2.getState());
    }
}
  • 使用Interrupt()方法尝试中断线程时的结果如下:

在这里插入图片描述

  • 使用stop方法尝试中断线程时的结果如下:

在这里插入图片描述

这里需要再提醒一下,在并发编程时,是不推荐使用stop进行中断线程的,因为stop会释放掉线程所有的资源。 —》 更推荐的是使用interrupt方法 — 》 可以看一下我的另一篇文章《【并发编程】— interrupt、interrupted和isInterrupted使用详解》。


2.2.2 ReentrantLock使用tryLock()加锁 — interrupt和stop都可中断

  • code
package com.nrsc.ch1.base.jmm.syn_study.texing;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ReentrantLockCanInterruptibleDemo {
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        // 1. 定义一个Runnable
        Runnable run = () -> {
            boolean flag = false;
            try {
                // 2.对方法进行加锁
                flag = lock.tryLock();
                String name = Thread.currentThread().getName();
                if (flag) {
                    log.info("线程{}进入同步代码块", name);
                    Thread.sleep(888888);  //保证不退出同步代码块
                } else {
                    log.info("线程{}未抢到锁", name);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (flag) {
                    lock.unlock();
                }
            }

        };

        // 3. 先开启一个线程来执行同步代码块
        Thread t1 = new Thread(run);
        t1.start();
        Thread.sleep(1000);

        // 4. 后开启一个线程来执行同步代码块(阻塞状态)
        Thread t2 = new Thread(run);
        t2.start();

        //等待5秒,确保已经抢了
        Thread.sleep(5000);

        log.info("线程t2尝试停止线程前");
        t2.interrupt(); //使用interrupt不可中断
        //t2.stop(); //使用stop可中断
        log.info("线程t2尝试停止线程后");

        log.info("线程t1的状态:{}", t1.getState());
        log.info("线程t2的状态:{}", t2.getState());
    }
}
  • 测试结果

在这里插入图片描述


简单提一句tryLock还可以指定在一定时间内尝试获取锁,如果获取不到,就不再获取了,这其实也可以避免追女朋友,追了很久很久很久,就是追不到,但是还不准许我不追的情况。
在这里插入图片描述


end

发布了225 篇原创文章 · 获赞 319 · 访问量 53万+

猜你喜欢

转载自blog.csdn.net/nrsc272420199/article/details/105180022