JUC高并发编程2:Lock接口

news2024/11/18 23:29:28

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux网络 —— 网络基础概念】

Linux网络 —— 网络基础概念 计算机网络背景网络发展 初始协议协议分层协议分层的好处 OSI七层模型TCP/IP五层(或四层)模型 再识协议为什么要有TCP/IP协议&#xff1f;什么是TCP/IP协议&#xff1f;TCP/IP协议与操作系统的关系所以究竟什么是协议&#xff1f; 网络传输基本流程…

【openwrt】 libubox组件——ustream

文章目录 ustream 核心数据结构struct ustreamstruct ustream_buf_liststruct ustream_bufstruct ustream_fd ustream 核心APIustream_fd_initustream_uloop_cbustream_fd_read_pendingustream_fill_read ustream_write_pendingustream_writeustream_fd_write ustream 应用示例…

Python画笔案例-059 绘制甩曲彩点动图

1、绘制甩曲彩点动图 通过 python 的turtle 库绘制 甩曲彩点动图,如下图: 2、实现代码 绘制甩曲彩点动图,以下为实现代码: """甩曲彩点动图.py """ import time import turtlecs = [red,orange,

CVPT: Cross-Attention help Visual Prompt Tuning adapt visual task

论文汇总 当前的问题 图1:在VTAB-1k基准测试上&#xff0c;使用预训练的ViT-B/16模型&#xff0c;VPT和我们的CVPT之间的性能和Flops比较。我们将提示的数量分别设置为1、10、20、50,100,150,200。 如图1所示&#xff0c;当给出大量提示时&#xff0c;VPT显示了性能的显著下降…

串口问题汇总:串口发送乱码,重定义使用printf ,输出顺序出错,缓存区思想,串口项目应用

1.c51使用串口出现顺序被覆盖的情况&#xff0c;也就是输出time 最后输出的却是te 这是因为你没有等待上一个数据发送就开始发送下一个数据就会导致数据篡位 2.c51想使用串口重定义使用printf 首先c51是自带stdio.h不需要像32那样点击 include lib选项&#xff0c;你直接改…

力扣958:判断二叉树是否为完全二叉树

给你一棵二叉树的根节点 root &#xff0c;请你判断这棵树是否是一棵 完全二叉树 。 在一棵 完全二叉树 中&#xff0c;除了最后一层外&#xff0c;所有层都被完全填满&#xff0c;并且最后一层中的所有节点都尽可能靠左。最后一层&#xff08;第 h 层&#xff09;中可以包含 …

体制内打工人收藏!5款AI写作工具,助你变成单位笔杆子~

对于初入体制内职场的新手或是日常任务繁重、难以抽身撰写文件的同事们&#xff0c;别再让加班的夜晚成为常态&#xff01;现在&#xff0c;就让我揭秘几个高效公文写作宝库&#xff0c;它们能助你迅速掌握公文写作的精髓&#xff0c;海量素材信手拈来&#xff0c;更有快速成文…

Elasticsearch、ik分词器、elasticsearch-head、Kibana的认识与安装

文章目录 elasticsearch安装elasticsearchIK中文分词器elasticsearch-headkibana elasticsearch Elasticsearch是一个基于Lucene的搜索服务器&#xff0c;也是属于NoSQL阵营的数据库。它提供了一个分布式多用户能力的全文搜索引擎&#xff0c;基于RESTful web接口提供给我们操…

2025年SEO策略:如何优化您的知识库?

如今很多人在遇到问题时都会求助于谷歌。谷歌已经成为提供解决方案不可或缺的工具。作为全球搜索引擎的巨头&#xff0c;拥有大量用户流量。这就是为什么确保您的产品和服务在谷歌搜索结果中排名靠前是至关重要的&#xff0c;如果您想获得更多的客户&#xff0c;SEO是一个非常关…

打造你的专属主题-VitePress保姆级教程

本篇为vitepress系列教程&#xff0c;在开始前&#xff0c;若还不了解vitepress的小伙伴可以看一下以往文章&#xff1a; 不敲一行代码&#xff01;助你快速搭建属于自己的官网博客&#xff01;-VitePress保姆级教程 文章目录 VitePress主题配置准备自定义主题配置标题配置图标…

如何用AI实现自动更新文章?(全自动更新网站)

AI的诞生确实给我们的生活和工作都带来了很大的改变&#xff0c;从我自身来讲&#xff0c;也渐渐习惯了遇到事情先问问AI&#xff0c;不管是翻译、专业性问题、PPT制作、总结写作这些&#xff0c;确实帮我迅速理清了思路&#xff0c;也可以有很多内容的借鉴。 作为一个业余爱好…

滑动窗口算法第一弹(长度最小的子数组,无重复字符的最长子串 最大连续1的个数III)

目录 前言 1. 长度最小的子数组 &#xff08;1&#xff09;题目及示例 &#xff08;2&#xff09;暴力解法 &#xff08;3&#xff09;优化 2. 无重复字符的最长子串 &#xff08;1&#xff09;题目及示例 &#xff08;2&#xff09;暴力解法 &#xff08;3&#xff…

深度学习:卷积神经网络CNN

目录 一、什么是卷积&#xff1f; 二、卷积神经网络的组成 1. 卷积层 2. 池化层 3. 激活函数 4. 全连接层 三、卷积神经网络的构造 四、代码实现 1.数据预处理 2.创建卷积神经网络 3.创建训练集和测试集函数 4.创建损失函数和优化器并进行训练 一、什么是卷积&…

Kivy,一个上天入地的 Python 库

大家好&#xff01;我是炒青椒不放辣&#xff0c;关注我&#xff0c;收看每期的编程干货。 一个简单的库&#xff0c;也许能够开启我们的智慧之门&#xff0c; 一个普通的方法&#xff0c;也许能在危急时刻挽救我们于水深火热&#xff0c; 一个新颖的思维方式&#xff0c;也许能…

USB 电缆中的信号线 DP、DM 的缩写由来

经常在一些芯片的规格书中看到 USB 的信号对是以 DP 和 DM 命名&#xff1a; 我在想&#xff0c;这些规格书是不是写错了&#xff0c;把 N 写成 M 了&#xff1f;DM 中的 M 到底是什么的缩写&#xff1f; 于是我找了一些资料&#xff0c;终于在《Universal Serial Bus Cables …

string 的介绍及使用

一.string类介绍 C语言中&#xff0c;字符串是以’\0’结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;不太符合OOP的思想&#xff0c;而且底层空间需要用户自己管理&a…

BUUCTF [SCTF2019]电单车详解两种方法(python实现绝对原创)

使用audacity打开&#xff0c;发现是一段PT2242 信号 PT2242信号 有长有短&#xff0c;短的为0&#xff0c;长的为1化出来 这应该是截获电动车钥匙发射出的锁车信号 0 01110100101010100110 0010 0前四位为同步码0 。。。中间这20位为01110100101010100110为地址码0010为功…

ssm病人跟踪治疗信息管理系统

专业团队&#xff0c;咨询就送开题报告&#xff0c;欢迎大家咨询留言 摘 要 病人跟踪治疗信息管理系统采用B/S模式&#xff0c;促进了病人跟踪治疗信息管理系统的安全、快捷、高效的发展。传统的管理模式还处于手工处理阶段&#xff0c;管理效率极低&#xff0c;随着病人的不断…

《SG-Former: Self-guided Transformer with Evolving Token Reallocation》ICCV2023

摘要 SG-Former&#xff08;Self-guided Transformer&#xff09;是一种新型的视觉Transformer模型&#xff0c;旨在解决传统Transformer在处理大型特征图时面临的计算成本高的问题。该模型通过一种自适应细粒度的全局自注意力机制&#xff0c;实现了有效的计算成本降低。它利…

VmWare安装虚拟机教程(centos7)

VMWare下载&#xff1a; 下载 VMware Workstation Pro - VMware Customer Connect 安装包&#xff1a;&#xff08;16的版本&#xff09;免费&#xff01;&#xff08;一个赞就行&#xff09; 一直点下一步即可&#xff0c;注意修改一下安装位置就好 二、安装虚拟机 安装虚…