惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Palo Alto Networks Blog
AWS News Blog
AWS News Blog
Latest news
Latest news
Microsoft Security Blog
Microsoft Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Vulnerabilities – Threatpost
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
C
CERT Recently Published Vulnerability Notes
T
Tenable Blog
雷峰网
雷峰网
K
Kaspersky official blog
人人都是产品经理
人人都是产品经理
C
Cybersecurity and Infrastructure Security Agency CISA
L
Lohrmann on Cybersecurity
T
The Exploit Database - CXSecurity.com
T
Tor Project blog
A
Arctic Wolf
Project Zero
Project Zero
F
Fortinet All Blogs
T
Threatpost
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
SegmentFault 最新的问题
P
Privacy & Cybersecurity Law Blog
D
DataBreaches.Net
量子位
Cisco Talos Blog
Cisco Talos Blog
博客园 - 【当耐特】
V
Visual Studio Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园 - 叶小钗
I
Intezer
G
GRAHAM CLULEY
Google Online Security Blog
Google Online Security Blog
T
The Blog of Author Tim Ferriss
T
Troy Hunt's Blog
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
Cyber Attacks, Cyber Crime and Cyber Security
Hacker News: Ask HN
Hacker News: Ask HN
M
MIT News - Artificial intelligence
N
News | PayPal Newsroom
Cyberwarzone
Cyberwarzone
V
V2EX
腾讯CDC
Webroot Blog
Webroot Blog
MongoDB | Blog
MongoDB | Blog

Liu Zijian's Blog | 一个技术博客

使用Certbot自动续签HTTPS证书 使用Filebeat采集Nginx日志到ES Python的协程 Python中的异常 Python中的类和对象 Python的函数 Python的数据结构,推导式、迭代器和生成器 Spring AI集成多模态模型 LangChain4j多模态 LangChain Tools工具使用 Python中的模块和包 Python全局环境和虚拟环境(venv) LangChain Prompt提示词工程 LangChain4j Tools工具使用 基于Dify搭建AI智能体应用 LangChain4j RAG检索增强生成 Spring AI实现MCP Server Spring AI集成MCP Client LangChain4j Prompt提示词工程 Spring AI使用知识库增强对话功能 Spring AI实现一个智能客服 Spring AI实现一个简单的对话机器人 实现MinIO数据的每日备份 自己实现一个DNS服务 简单理解AI智能体 大模型和大模型应用 LangChain开篇 LangChain4j开篇 一个解析Excel2007的POI工具类 DataPermissionInterceptor源码解读 TenantLineInnerInterceptor源码解读 BaseMultiTableInnerInterceptor源码解读 Spring AI开篇 SQL解析工具JSQLParser 芋道源码解读之多租户 芋道源码解读之数据权限 芋道源码解读开篇 Java实现将数据导出为Word文档 OA系统的天数该怎样计算 安装MySQL8 安装MySQL5.7 RockyLinux9环境下编译MySQL8 MySQL字符集及底层原理 Java实现LDAP登录 Docker Compose IPv4和IPv6 使用虚拟机安装一个K8s集群 使用GraalVM原生编译打包SpringBoot工程 Nginx防止目录穿越 Java线程的状态 Nginx防盗链设置 使用python将excel表格转换为SQL INSERT Redis的公共操作命令 Redis数据结构之Bitfleid Redis数据结构之Bitmap Redis数据结构之GEO Redis数据结构之Hash Redis数据结构之HyperLogLog Redis数据结构之List Redis数据结构之Set Redis数据结构之Stream Redis数据结构之String Redis数据结构之ZSet 使用python压缩图片 利用Python实现Hexo站点的持续集成 Nginx设置HTTPS监听 firewalld防火墙工具的使用 Linux信号(signal)机制 MySQL5.7x 主从复制 用IP自签发一个HTTPS证书 基于Hexo实现一个静态的个人博客 RockyLinux9环境下编译MySQL5.7 Docker离线安装 MySQL数据定义语言 Docker与联合文件系统 Docker的网络 Docker的镜像操作 MySQL存储过程 MyBatis-Plus开篇 MySQL变量 MySQL视图 MySQL事务 MySQL插入修改和删除 MySQL查询 MySQL系统命令 Docker的容器操作 Docker的安装和配置 Docker容器数据卷 浅谈OAuth2.0授权原理 JVM开篇 浅谈Linux(Unix)的I/O模型 一个通用的CloseableHttpClient工厂类 JUC读写锁ReadWriteLock Java的单例 Java泛型 Java8的新特性 最近最少使用算法(LRU) MySQL函数 SpringBoot配置和启动 volatile作用分析
JUC可重入锁ReentrantLock
Liu Zijian · 2022-09-23 · via Liu Zijian's Blog | 一个技术博客

1.概述

java.util.concurrent.locks.ReentrantLock,即可重入锁,是Lock接口的一个实现类,可以实现和synchronized一样的功能

以往我们都是使用synchronized解决线程的安全问题,在JUC中,java.util.concurrent.locks.Lock接口提供了比synchronized更多的功能,可以使用Lock接口实现手动上锁和释放锁,并且可以更加精准的设置条件(Condition)控制等待唤醒,它的特点以及和synchronized的区别是:

  1. synchronized是Java中的关键字,而Lock则是Java中的一个类。
  2. synchronized的上锁和释放锁都是自动完成,而使用Lock时必须手动释放锁,否则会造成死锁现象。
  3. Lock可以让等待锁的线程中断,而synchronized却不能,使用synchronized时,未得到锁的线程会一直等待下去不会中断。
  4. 通过Lock可以知道是否成功获取到锁,而synchronized却不能。
  5. Lock可以提高多个线程进行读操作的效率,在大量线程同时竞争时,效率远胜于synchronized。

2.获取锁和释放锁

使用lock.lock();方法实现上锁,用lock.unlock();方法释放锁,一般情况下,lock.unlock();的代码总是要写在finally中,以保证方法出现异常后也能释放锁,避免死锁。

例:10个线程卖票,每次只能有一个线程对票数进行修改,JUC中所有的例子都可以写成“线程操作资源类”的形式,本例创建10个线程共同操作资源类TicketTask,调用ticket()方法卖票

package example.juc.test2;

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

public class TestLock {

    public static void main(String[] args) {

        TicketTask ticketTask = new TicketTask();

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {

                ticketTask.ticket();

            }, "线程" + i).start();
        }
    }
}


class TicketTask {

    private int number = 500;
    private Lock lock = new ReentrantLock();

    public void ticket() {

        while (true) {
            lock.lock();
            try {
                if (number > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    number--;
                    System.out.println(Thread.currentThread().getName() + "完成售票:" + number);
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }

        }

    }
}

3.公平与非公平

synchronized默认是非公平的,ReentrantLock也是默认非公平锁,比如以下例子运行结果会出现卖的票被同一个线程大量抢占的情况

package example.juc.test2;

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

public class TestLock {

    public static void main(String[] args) {

        TicketTask ticketTask = new TicketTask();


        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                ticketTask.ticket();
            }

        }, "线程A").start();

        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                ticketTask.ticket();
            }

        }, "线程B").start();

        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                ticketTask.ticket();
            }

        }, "线程C").start();

    }
}


class TicketTask {

    private int number = 30;
    private Lock lock = new ReentrantLock();

    public void ticket() {

        lock.lock();
        try {
            if (number > 0) {
                try {
                    Thread.sleep(10);
                }
                catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                number--;
                System.out.println(Thread.currentThread().getName() + "完成售票:" + number);
            }
        } 
        finally {
            lock.unlock();
        }

    }

}

出现这种情况的原因就在于,ReentrantLock默认是非公平的

java.util.concurrent.locks.ReentrantLock

public ReentrantLock() {
    sync = new NonfairSync();
}

......

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

如果想要实现公平的效果,创建对象时需要加上一个初始化参数

private Lock lock = new ReentrantLock(true);

公平锁的优点在于“公平”,缺点在于效率较低

4.可重入

可重入锁指的是,一个线程在一个方法外层获取了锁 ,进入方法内层会自动获取锁。

和synchronized一样,ReentrantLock也是可重入的,但是需要注意加锁和释放次数要匹配,否则其他线程无法获得锁,例如:

package example.juc3;

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

public class TestReentrantLock {

    public static void main(String[] args) {

        Lock lock = new ReentrantLock();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("外层");
                lock.lock();
                try {
                    System.out.println("内层");
                } finally {
                    lock.unlock();
                }
            } finally {
                lock.unlock();
            }

        },"t1").start();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("2");
            } finally {
                lock.unlock();
            }
        },"t2").start();

    }
}

5.响应中断

synchronized是无法支持响应中断的,一个线程获取不到锁,就会一直等着,程序无法结束,造成死锁。

而ReentrantLock支持通过tryLock()设置一个时间参数,到时间后自动放弃获取锁,如果不设置时间参数代表立即返回获取锁的结果,通过这个可以避免死锁现象

例1:派发1000个线程,每个为变量加1,结果抢不到的直接放弃,所以变量被加的远远不到1000

package example.juc2.test;

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

public class TestTryLock {
    
    private static final Lock lock = new ReentrantLock();
    private static int sum = 0;

    public static void main(String[] args) {

        for (int i = 0; i < 1000; i++) {

            new Thread(() -> {

                if (lock.tryLock()) {
                    try {
                        sum ++;
                        System.out.println(Thread.currentThread().getName() +" - "+ sum);
                    }
                    catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    finally {
                        lock.unlock();
                    }

                }

            }).start();
        }
    }


}

例2:每个抢到锁的线程1秒钟才能执行完成,每个抢不到锁的线程最多等待5秒否则直接放弃争抢锁,因此程序只能打印5次左右

package example.juc2.test;

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

public class TestTryLock {
    
    private static final Lock lock = new ReentrantLock();
    private static int sum = 0;

    public static void main(String[] args) {

        for (int i = 0; i < 1000; i++) {

            new Thread(() -> {

                try {
                    if (lock.tryLock(5, TimeUnit.SECONDS)) {
                        try {
                            sum ++;
                            System.out.println(Thread.currentThread().getName() +" - "+ sum);
                            TimeUnit.SECONDS.sleep(1);
                        }
                        catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                        finally {
                            lock.unlock();
                        }

                    }
                }
                catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }).start();
        }
    }

}

6.精准唤醒

如果说Lock代替了synchronized的使用,Condition则是替代了Object的wait()notify()notifyAll()方法,java.util.concurrent.locks.Condition是一个接口,通过调用Lock对象的newCondition()方法获取具体Condition对象,将Condition绑定在Lock上,通过Condition的await()signal()signalAll()实现线程之间的通信,与传统Object作为同步监视器一样,Condition的await()也要总是出现在循环中,实现二次条件判断。

例:实现一个资源类AirConditioner,然后新建4个线程,每个线程对AirConditioner中的变量number交替改成0和1

package example.juc2.test;

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

public class TestConditionWaitNotify {
    
    public static class AirConditioner {
        
        private int number = 0;
        
        private Lock lock = new ReentrantLock();
        private Condition condition = lock.newCondition();

        public void increment() {
            lock.lock();
            try {
                while (number != 0) {
                    condition.await();
                }
                number ++;
                System.out.println(Thread.currentThread().getName()+" 修改为 "+number);
                condition.signalAll();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
            
        }

        public void decrement() {
            lock.lock();
            try {
                while (number == 0) {
                    condition.await();
                }
                number --;
                System.out.println(Thread.currentThread().getName()+" 修改为 "+number);
                condition.signalAll();
            } 
            catch (InterruptedException e) {
                throw new RuntimeException(e);
            } 
            finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        AirConditioner airConditioner = new AirConditioner();

        new Thread(() -> {
            for (int i=0;i<10;i++) {
                airConditioner.increment();
            }
        }, "T1").start();

        new Thread(() -> {
            for (int i=0;i<10;i++) {
                airConditioner.decrement();
            }
        }, "T2").start();

        new Thread(() -> {
            for (int i=0;i<10;i++) {
                airConditioner.increment();
            }
        }, "T3").start();

        new Thread(() -> {
            for (int i=0;i<10;i++) {
                airConditioner.decrement();
            }
        }, "T4").start();

    }
}

运行结果:

T1 修改为 1
T2 修改为 0
T1 修改为 1
T2 修改为 0
T3 修改为 1
T2 修改为 0
T3 修改为 1
T4 修改为 0
T3 修改为 1
T4 修改为 0
T3 修改为 1
T4 修改为 0
T3 修改为 1
T4 修改为 0
T3 修改为 1
T4 修改为 0
T3 修改为 1
T4 修改为 0
T3 修改为 1
T4 修改为 0
T3 修改为 1
T4 修改为 0
T3 修改为 1
T4 修改为 0
T1 修改为 1
T4 修改为 0
T1 修改为 1
T2 修改为 0
T1 修改为 1
T2 修改为 0
T1 修改为 1
T2 修改为 0
T1 修改为 1
T2 修改为 0
T1 修改为 1
T2 修改为 0
T1 修改为 1
T2 修改为 0
T1 修改为 1
T2 修改为 0

上面的例子中,线程对资源类的操作是无序的,和加了synchronized的效果一样,而Condition有一个比synchronized更强大的地方,那就是能精确的控制等待唤醒。如果希望几个线程有序的交替执行,就可以获取多个监视器Condition绑定同一个Lock,实现精确的定制化通信。

例:三个线程ABC,按A-B-C顺序执行资源类ShareResource中的打印任务,并循环交替3轮(ABC-ABC-ABC),A执行时打印5次,B10次,C15次,通过lock.newCondition()为三个线程分别创建3个同步监视器:conditionA,conditionB, conditionC,再通过标志位变量flag判断当前该谁执行了,开始默认为A。

程序执行,A线程先获得执行权,执行第一轮,执行完成唤醒B,执行第一轮,B线程执行完成唤醒C,C也执行第一轮,完成再唤醒A,A开始执行第二轮,以此类推直到三轮任务全部完成,实现每一轮都是按照A-B-C的顺序执行。。

package example.juc2.test;

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

public class TestThreadOrderAccess {
    
    public static class ShareResource {
        
        private char flag = 'A';
        
        private final Lock lock = new ReentrantLock();
        
        private final Condition conditionA = lock.newCondition();
        private final Condition conditionB = lock.newCondition();
        private final Condition conditionC = lock.newCondition();
        
        public void doA() {
            lock.lock();
            try {
                while (flag != 'A') {
                    conditionA.await();
                }

                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "\t" +i);
                }
                
                flag = 'B';
                conditionB.signal();
            } 
            catch (InterruptedException e) {
                throw new RuntimeException(e);
            } 
            finally {
               lock.unlock(); 
            }
        }

        public void doB() {
            lock.lock();
            try {
                while (flag != 'B') {
                    conditionB.await();
                }

                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "\t" +i);
                }

                flag = 'C';
                conditionC.signal();
            } 
            catch (InterruptedException e) {
                throw new RuntimeException(e);
            } 
            finally {
                lock.unlock();
            }
        }

        public void doC() {
            lock.lock();
            try {
                while (flag != 'C') {
                    conditionC.await();
                }

                for (int i = 0; i < 15; i++) {
                    System.out.println(Thread.currentThread().getName() + "\t" +i);
                }

                System.out.println("*********");

                flag = 'A';
                conditionA.signal();
            } 
            catch (InterruptedException e) {
                throw new RuntimeException(e);
            } 
            finally {
                lock.unlock();
            }
        }
        
    }

    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                shareResource.doA();
            }
        }, "A").start();
        
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                shareResource.doB();
            }
        }, "B").start();
        
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                shareResource.doC();
            }
        }, "C").start();
    }
}

最终运行结果:

A	0
A	1
A	2
A	3
A	4
B	0
B	1
B	2
B	3
B	4
B	5
B	6
B	7
B	8
B	9
C	0
C	1
C	2
C	3
C	4
C	5
C	6
C	7
C	8
C	9
C	10
C	11
C	12
C	13
C	14
*********
A	0
A	1
A	2
A	3
A	4
B	0
B	1
B	2
B	3
B	4
B	5
B	6
B	7
B	8
B	9
C	0
C	1
C	2
C	3
C	4
C	5
C	6
C	7
C	8
C	9
C	10
C	11
C	12
C	13
C	14
*********
A	0
A	1
A	2
A	3
A	4
B	0
B	1
B	2
B	3
B	4
B	5
B	6
B	7
B	8
B	9
C	0
C	1
C	2
C	3
C	4
C	5
C	6
C	7
C	8
C	9
C	10
C	11
C	12
C	13
C	14
*********

7.总结

Lock和synchronized都是独占锁,可重入锁,但是synchronized获取/释放锁是JVM自动完成,Lock是需要开发者手动完成。synchronized不能响应中断,Lock可以响应中断,synchronized无法精准唤醒,Lock可以实现精准唤醒。