JUC高并发编程2:Lock接口

1 synchronized

1.1 synchronized关键字回顾

synchronized 是 Java 中的一个关键字,用于实现线程间的同步。它提供了一种简单而有效的方式来控制对共享资源的访问,从而避免多个线程同时访问同一资源时可能出现的竞态条件(race condition)和数据不一致问题。

1.1.1 主要用途

synchronized 关键字可以用于以下两种场景:

  1. 同步方法(Synchronized Methods)

    • 当一个方法被声明为 synchronized 时,该方法在同一时刻只能被一个线程执行。
    • 如果一个对象有多个 synchronized 方法,那么同一时刻只能有一个线程执行这些方法中的任意一个。
    public synchronized void method() {
        // 方法体
    }
    
  2. 同步代码块(Synchronized Blocks)

    • synchronized 关键字也可以用于代码块,从而只同步方法中的某一部分代码。
    • 同步代码块需要指定一个对象作为锁(通常是 this 或某个特定的对象)。
    public void method() {
        synchronized (this) {
            // 同步代码块
        }
    }
    

1.1.2 售票案例

// 第一步 创建资源类,定义属性和操作方法

class Ticket{
    //票数
    private int number = 30;
    // 操作方法:卖票
    public synchronized void sale(){
        // 判断:是否有票
        if(number > 0) {
            System.out.println(Thread.currentThread().getName() + " : 卖出: " + (number--) + " 剩余:" + number);
        }
    }
}
public class SaleTicket {

    // 第二步:创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        // 创建Ticket对象
        Ticket ticket = new Ticket();

        // 创建三个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用卖票的方法
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }
        },"AA").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用卖票的方法
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }
        },"BB").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                // 调用卖票的方法
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }
        },"CC").start();

    }
}

如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时 JVM 会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用 sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到。

2 什么是Lock

Lock 接口是 Java 并发包(java.util.concurrent.locks)中提供的一种更灵活、更强大的同步机制,用于替代传统的 synchronized 关键字。与 synchronized 相比,Lock 提供了更多的控制选项和功能,使得开发者能够更精细地控制锁的行为。

2.1 Lock接口介绍

2.1.1 主要特点

  1. 显式锁

    • Lock 是一个接口,需要通过具体的实现类(如 ReentrantLock)来使用。
    • synchronized 不同,Lock 需要显式地获取和释放锁,这使得代码更加灵活,但也要求开发者必须手动管理锁的生命周期。
  2. 灵活性

    • Lock 提供了多种获取锁的方式,如 lock()tryLock()lockInterruptibly() 等,使得开发者可以根据具体需求选择合适的锁获取方式。
    • tryLock() 方法允许在获取锁失败时立即返回,而不是阻塞等待,这有助于避免死锁。
  3. 公平性

    • Lock 接口支持公平锁和非公平锁。公平锁会按照线程请求锁的顺序来分配锁,而非公平锁则允许插队(即新来的线程可以抢占锁)。
  4. 条件变量(Condition)

    • Lock 接口提供了 newCondition() 方法,用于创建条件变量(Condition),这类似于 Objectwait()notify()notifyAll() 方法,但提供了更强大的功能。

2.1.2 主要方法

  • void lock()

    • 获取锁,如果锁不可用,则当前线程会被阻塞,直到锁被释放。
  • void lockInterruptibly() throws InterruptedException

    • 获取锁,如果锁不可用,则当前线程会被阻塞,直到锁被释放或当前线程被中断。
  • boolean tryLock()

    • 尝试获取锁,如果锁可用则立即返回 true,否则返回 false,不会阻塞。
  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException

    • 尝试在指定时间内获取锁,如果在指定时间内锁可用则返回 true,否则返回 false
  • void unlock()

    • 释放锁。
  • Condition newCondition()

    • 返回一个与该锁关联的 Condition 实例。

2.2 Lock实现可重入锁

2.2.1 卖票案例

// 第一步 创建资源类,定义属性和操作方法

class LTicket {
    // 创建可重入锁
    private final ReentrantLock lock = new ReentrantLock();
    // 票数量
    private int number = 30;

    // 卖票方法
    public void sale() {
        // 上锁
        lock.lock();

        try {
            // 判断:是否有票
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + " : 卖出: " + (number--) + " 剩余:" + number);
            }

        } finally {

            // 解锁
            lock.unlock();
        }

    }
}

public class LSaleTicket {

    // 第二步:创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        // 创建Ticket对象
        LTicket ticket = new LTicket();

        // 创建三个线程
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"AA").start();


        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"CC").start();
    }
}

2.3 小结

Lock 和 synchronized 有以下几点不同:

  1. Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内
    置的语言实现;
  2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现
    象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很
    可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
  3. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用
    synchronized 时,等待的线程会一直等待下去,不能够响应中断;
  4. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
  5. Lock 可以提高多个线程进行读操作的效率。
    在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源
    非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于
    synchronized。

3 创建线程的多种方式

在 Java 中,创建线程的方式主要有以下几种:

3.1 继承 Thread

通过继承 Thread 类并重写其 run() 方法来创建线程。

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread is running by extending Thread class");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

3.2 实现 Runnable 接口

通过实现 Runnable 接口并重写其 run() 方法来创建线程。这种方式更为灵活,因为 Java 不支持多重继承,但可以实现多个接口。

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread is running by implementing Runnable interface");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

3.3 使用 CallableFuture

Callable 接口类似于 Runnable,但它可以返回一个结果,并且可以抛出异常。Future 用于获取 Callable 任务的执行结果。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Thread is running by implementing Callable interface";
    }
}

public class Main {
    public static void main(String[] args) {
        MyCallable callable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();

        try {
            System.out.println(futureTask.get());  // 获取 Callable 的返回值
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

3.4 使用线程池(ExecutorService

通过 ExecutorService 接口和 Executors 工具类来创建线程池,从而管理多个线程的执行。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        executorService.submit(() -> {
            System.out.println("Thread is running using ExecutorService");
        });

        executorService.shutdown();
    }
}

3.5 使用 CompletableFuture(Java 8 及以上)

CompletableFuture 是 Java 8 引入的一个强大的异步编程工具,可以用于创建和管理异步任务。

import java.util.concurrent.CompletableFuture;

public class Main {
    public static void main(String[] args) {
        CompletableFuture.runAsync(() -> {
            System.out.println("Thread is running using CompletableFuture");
        });
    }
}

3.6 小结

  • 继承 Thread:简单直接,但不够灵活。
  • 实现 Runnable 接口:更灵活,适用于需要实现多个接口的场景。
  • 使用 CallableFuture:适用于需要返回结果的场景。
  • 使用线程池(ExecutorService:适用于需要管理多个线程的场景。
  • 使用 CompletableFuture:适用于异步编程和复杂的任务链。

选择哪种方式取决于具体的应用场景和需求。

4 附录 思维导图

在这里插入图片描述

5 参考链接

【【尚硅谷】大厂必备技术之JUC并发编程】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/882733.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

.net core8 使用JWT鉴权(附当前源码)

说明 该文章是属于OverallAuth2.0系列文章&#xff0c;每周更新一篇该系列文章&#xff08;从0到1完成系统开发&#xff09;。 该系统文章&#xff0c;我会尽量说的非常详细&#xff0c;做到不管新手、老手都能看懂。 说明&#xff1a;OverallAuth2.0 是一个简单、易懂、功能强…

LeetcodeTop100 刷题总结(二)

LeetCode 热题 100&#xff1a;https://leetcode.cn/studyplan/top-100-liked/ 文章目录 八、二叉树94. 二叉树的中序遍历&#xff08;递归与非递归&#xff09;补充&#xff1a;144. 二叉树的前序遍历&#xff08;递归与非递归&#xff09;补充&#xff1a;145. 二叉树的后序遍…

你的提交信息还在拖后腿?看这里,提升代码质量的绝招!

文章目录 前言一、什么是约定式提交&#xff1f;二、创建新仓库三、将代码推送到远程仓库的步骤1.检查当前远程仓库2.添加代码到暂存区3. 进行约定式提交4. 推送代码到远程仓库5. 完成推送 总结 前言 在当今软件开发领域&#xff0c;Git已经成为最广泛使用的版本控制系统之一。…

java算法OJ(1)位运算

目录 1.前言 2.正文 2.1位运算符号 2.1俩数相除 2.1.1题目 2.1.2示例 2.1.3题解 2.2二进制求和 2.2.1题目 2.2.2示例 2.2.3题解 2.3只出现一次的数字 2.3.1题目 2.3.2示例 2.3.3题解 2.4只出现一次的数字&#xff08;进阶版&#xff09; 2.4.1题目 2.4.2示例…

【ComfyUI】控制光照节点——ComfyUI-IC-Light-Native

原始代码&#xff08;非comfyui&#xff09;&#xff1a;https://github.com/lllyasviel/IC-Light comfyui实现1&#xff08;600星&#xff09;&#xff1a;https://github.com/kijai/ComfyUI-IC-Light comfyui实现2&#xff08;500星&#xff09;&#xff1a;https://github.c…

cobbler自动批量安装多版本操作系统

本次虚拟化环境为VMware Workstation Pro&#xff0c;cobbler服务端为CentOS7.9&#xff0c;需要自动安装的版本为CentOS7.9和CentOS8.1 目录 一、安装cobbler服务端1、修改YUM源2、关闭防火墙3、安装软件包4、cobbler环境配置5、解决语法问题6、启动服务7、导入镜像8、自定义…

Spring自定义参数解析器

在这篇文章中&#xff0c;我们认识了参数解析器和消息转换器&#xff0c;今天我们来自定义一个参数解析器。 自定义参数解析器 实现HandlerMethodArgumentResolver的类&#xff0c;并注册到Spring容器。 Component&#xff0f;&#xff0f;注册到Spring public class UserAr…

统信服务器操作系统【Cron定时任务服务】

Cron定时任务服务服务介绍、服务管理、服务配置 文章目录 一、功能概述二、功能介绍1. Cron 服务管理2.Cron 服务管理3.Cron 服务配置run-parts一、功能概述 cron是一个可以用来根据时间、日期、月份、星期的组合来 调度对周期性任务执行的守护进程。利用 cron 所提供的功能,可…

第十四届蓝桥杯嵌入式国赛

一. 前言 本篇博客主要讲述十四届蓝桥杯嵌入式的国赛题目&#xff0c;包括STM32CubeMx的相关配置以及相关功能实现代码以及我在做题过程中所遇到的一些问题和总结收获。如果有兴趣的伙伴还可以去做做其它届的真题&#xff0c;可去 蓝桥云课 上搜索历届真题即可。 二. 题目概述 …

七种修复错误:由于找不到msvcr110.dll 无法继续执行的方法

当你在运行某些程序时遇到“找不到msvcr110.dll”的错误提示&#xff0c;这通常意味着你的系统缺少了Microsoft Visual C 2012 Redistributable包中的一个重要文件。这个DLL文件是Microsoft Visual C Redistributable的一部分&#xff0c;用于支持许多使用Visual C编写的软件和…

Elasticsearch:检索增强生成背后的重要思想

作者&#xff1a;来自 Elastic Jessica L. Moszkowicz 星期天晚上 10 点&#xff0c;我九年级的女儿哭着冲进我的房间。她说她对代数一无所知&#xff0c;注定要失败。我进入超级妈妈模式&#xff0c;却发现我一点高中数学知识都不记得了。于是&#xff0c;我做了任何一位超级妈…

多颜色绘制语义分割/变化检测结果图

在论文绘图时&#xff0c;传统的二元语义分割结果图颜色单一&#xff08;下图左&#xff09;&#xff0c;所以论文中常根据混淆矩阵类别使用多颜色进行绘制&#xff08;下图右&#xff09;&#xff0c;可以看到&#xff0c;结果的可视化效果更好。 以下是绘制代码&#xff1a; …

Windows系统的Tomcat日志路径配置

文章目录 引言I Windows系统的Tomcat日志路径配置配置常规日志路径访问日志路径配置,修改server.xmlII 日志文件切割:以分隔割tomcat 的 catalina.out 文件为例子通过Linux系统自带的切割工具logrotate来进行切割引言 需求:C盘空间不足,处理日志文件,tomcat日志迁移到D盘…

Java基础知识扫盲

目录 Arrays.sort的底层实现 BigDecimal(double)和BigDecimal(String)有什么区别 Char可以存储一个汉字吗 Java中的Timer定时调度任务是咋实现的 Java中的序列化机制是咋实现的 Java中的注解是干嘛的 Arrays.sort的底层实现 Arrays.sort是Java中提供的对数组进行排序的…

信用卡存量经营读书笔记

信用卡的各项收益和损失分析表 用杜邦分析法拆利润如下 信用卡要不要烧钱&#xff1f;不要&#xff0c;因为没有网络效应&#xff08;用户量增加带来的优惠比较少&#xff09;和赢家通吃的情况 线上获客的几种方式&#xff1a;引流分成、某个项目的联名信用卡、营业收入分成 …

爬虫到底难在哪里?

如果你是自己做爬虫脚本开发&#xff0c;那确实难&#xff0c;因为你需要掌握Python、HTML、JS、xpath、database等技术&#xff0c;而且还要处理反爬、动态网页、逆向等情况&#xff0c;不然压根不知道怎么去写代码&#xff0c;这些技术和经验储备起码得要个三五年。 比如这几…

【D3.js in Action 3 精译_023】3.3 使用 D3 将数据绑定到 DOM 元素

当前内容所在位置&#xff1a; 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可视化最佳实践&#xff08;下&#xff09;1.4 本…

【开源免费】基于SpringBoot+Vue.JS教师工作量管理系统(JAVA毕业设计)

本文项目编号 T 043 &#xff0c;文末自助获取源码 \color{red}{T043&#xff0c;文末自助获取源码} T043&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

两数之和、三数之和、四数之和

目录 两数之和 题目链接 题目描述 思路分析 代码实现 三数之和 题目链接 题目描述 思路分析 代码实现 四数之和 题目链接 题目描述 思路分析 代码实现 两数之和 题目链接 LCR 179. 查找总价格为目标值的两个商品 - 力扣&#xff08;LeetCode&#xff09; 题目…

算法:69.x的平方根

题目 链接&#xff1a;leetcode链接 思路分析&#xff08;二分算法&#xff09; 当然你可以使用暴力查找&#xff0c;但是二分算法的时间复杂度更好。 我们先用暴力查找找点灵感 x &#xff1a;1 2 3 4 5 6 7 8 x2&#xff1a;1 4 9 16 25 36 49 64 我们的目的是找到一个x…