【并发编程】 --- 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

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
synchronizedReentrantLock是Java中用于实现线程同步的机制,它们在某些方面有相似之处,同时也存在一些不同点。下面是它们的相同点和不同点: 相同点: 1. 目的:synchronizedReentrantLock都是为了实现线程之间的同步,保证多个线程对共享资源的安全访问。 2. 实现原理:它们都采用了独占锁(互斥锁)的机制,即同一时间只允许一个线程访问被锁保护的代码块或方法。 不同点: 1. 获取与释放锁的方式:synchronized是隐式锁,即在进synchronized代码块或方法时自动获取锁,退出时自动释放锁;而ReentrantLock是显式锁,需要手动获取和释放锁。 2. 可synchronized是可锁,即同一个线程可以多次获取同一把锁;而ReentrantLock也是可锁,但需要手动实现。 3. 等待可中断synchronized不支持等待可中断,即线程无法响应中断请求;而ReentrantLock支持等待可中断,可以响应中断请求。 4. 公平synchronized是非公平锁,即无法保证等待时间最长的线程最先获取锁;而ReentrantLock可以通过构造函数指定是否为公平锁。 5. 可选择:使用synchronized时,无法对锁的行为进行更高级的控制;而ReentrantLock提供了更多的灵活和扩展,可以进行更高级的操作,如获取锁的超时时间、条件变量等。 6. 能:在低并发情况下,synchronized能优于ReentrantLock;但在高并发情况下,ReentrantLock能优于synchronized。 总结: synchronizedReentrantLock都是用于实现线程同步的机制,它们在实现目的和基本原理上相似,但在获取/释放锁的方式、可、等待可中断、公平、灵活能等方面存在一些不同点。在具体应用中,可以根据需求选择合适的锁机制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值