【JavaEE】多线程进阶(2)

【JavaEE】多线程进阶(2)

  • 一、JUC(java.util.concurrent) 的常⻅类
      • 1.1 Callable 接⼝
      • 1.2 ReentrantLock
      • 1.3 原子类
        • 原子类的特性:
        • 常见原子类:
        • 原子类的实例:
      • 1.4 线程池
      • 1.5 信号量 Semaphore
        • 代码实例
      • 1.6 CountDownLatch
        • 代码实例
      • 1.7 线程安全的集合类
        • 多线程环境使⽤ ArrayList
        • 多线程环境使⽤哈希表
        • Hashtable
        • ConcurrentHashMap
      • 1.8 死锁

一、JUC(java.util.concurrent) 的常⻅类

博客结尾附有此篇博客的全部代码!!!

1.1 Callable 接⼝

Callable 接口是 Java 中用于定义可以返回结果的任务的接口,它位于 java.util.concurrent 包中。

public interface Callable<V> {
    V call() throws Exception;
}

实例应用:计算1+2+…+100的值,使用Callable接口

   public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<Integer> callable = new Callable<Integer>() {
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i <= 100; i++) {
                    sum += i;
                }
                return sum;
            }
        };
        Thread thread = new Thread(callable);
        thread.start();
    }

在这里插入图片描述
原因:Thread本身不提供接受结果的方法,需要FutureTask对象来拿到结果(Thread不提供接受结果是为了更好的解耦合,将任务和线程分离开)

  • FutureTask:FutureTask 实现了 Runnable 接口,因此可以被 Thread 接受。
  • Thread类的构造函数可以接受一个 Runnable 对象,但不能接受其他类型的对象,因为 Thread 的内部逻辑是基于 Runnable 的 run() 方法实现的。

修改:

public class CallableDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<Integer> callable=new Callable<Integer>() {
            public Integer call() throws Exception {
                int sum=0;
                for (int i = 0; i <= 100; i++) {
                    sum+=i;
                }
                return sum;
            }
        };
        FutureTask<Integer> futureTask=new FutureTask<>(callable);
        Thread thread=new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }
}

通过Runnable接口计算1+2+…+100的值:

public class RunnableDemo {
    private static int total=0;
    public static void main(String[] args) throws InterruptedException {
        Runnable r = new Runnable(){
            int sum=0;
            public void run() {
                for (int i = 0; i <=100 ; i++) {
                    sum+=i;
                }
                total=sum;
            }
        };
        Thread t1 = new Thread(r);
        t1.start();
        t1.join();
        System.out.println(total);
    }
}

1.2 ReentrantLock

可重⼊互斥锁. 和 synchronized 定位类似, 都是⽤来实现互斥效果, 保证线程安全

ReentrantLock 的核心功能是通过 Lock 接口实现的,它提供了以下方法:

  • lock():获取锁,如果锁已经被其他线程占用,则当前线程会阻塞,直到获取锁。
  • unlock():释放锁。
  • tryLock():尝试获取锁,如果锁可用则立即获取,否则返回 false,不会阻塞。
  • tryLock(long timeout, TimeUnit unit):尝试获取锁,如果在指定时间内无法获取锁,则返回 false。
  • isHeldByCurrentThread():判断当前线程是否持有该锁。
  • isLocked():判断锁是否被任何线程持有。
public class ReentrantLockDemo1 {
    private static int total = 0;
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock locker = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                locker.lock();
                total++;
                locker.unlock();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                locker.lock();
                total++;
                locker.unlock();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(total);
    }
}

运行结果:total=100000
这里需要注意的:
因为这里解锁需要自己手动解锁,但是不可避免的抛出异常而导致代码运行终止,有可能就执行不到 locker.lock();
改进:将unlocker.lock();放入finally代码块中
在这里插入图片描述
ReentrantLock和synchronized对比:

  1. synchronized是关键字,ReentrantLock是Java的标准库中的类
  2. synchronized是通过代码块执行加锁解锁,而ReentrantLock是通过lock()和unlock()加锁解锁,需要注意的是unlock()不调用问题
  3. ReentrantLock提供的tryLock(),如果成功加锁,返回true;反之,加锁失败,返回false,不会出现阻塞;而且还可以设置等待时长,在这段时间后再尝试加锁,返回true/false。
  4. synchronized是非公平锁,ReentrantLock默认是非公平锁,但是可以设置为公平锁
ReentrantLock lock = new ReentrantLock(true);
  1. 更强⼤的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是⼀个随机等待的线程. ReentrantLock 搭配Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定线程。

1.3 原子类

原子类通过提供一系列线程安全的变量操作方法,确保在多线程环境下对变量的读写操作是不可分割的(即原子的)。它们利用了底层硬件的原子操作指令(如 CAS),从而避免了锁的开销,提高了性能。

原子类的特性:
  • 无锁并发:原子类通过 CAS 机制实现线程安全,无需使用重量级的锁(如 synchronized 或 ReentrantLock)。
  • 高性能:由于避免了锁的开销,原子类在高并发场景下通常比传统同步机制性能更高。
  • 线程安全:原子类保证了对变量的操作是原子的,即使在多线程环境下也不会出现竞态条件。
常见原子类:

(1)基本类型原子类:
AtomicInteger:用于原子操作的整数。
AtomicLong:用于原子操作的长整型。
AtomicBoolean:用于原子操作的布尔值。
(2)引用类型原子类:
AtomicReference:用于原子操作的对象引用。
AtomicStampedReference:用于原子操作的对象引用,同时带有版本号(用于解决 ABA 问题)。
AtomicMarkableReference:用于原子操作的对象引用,同时带有布尔标记。
(3)数组类型原子类:
AtomicIntegerArray:用于原子操作整型数组。
AtomicLongArray:用于原子操作长整型数组。
AtomicReferenceArray:用于原子操作对象引用数组。

原子类的实例:

基本类型原子类:AtomicInteger:用于原子操作的整数

public class AtomicIntegerArrayDemo1 {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(2);
        atomicInt.incrementAndGet(); // 增加 1
        atomicInt.addAndGet(2);      // 增加 5
        atomicInt.compareAndSet(5, 10); // 如果当前值为 5,则设置为 10
        System.out.println(atomicInt.get());//这里获取的是10
    }
}
public class AtomicIntegerArrayDemo {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicInt = new AtomicInteger(0);

        Thread t1 = new Thread(() -> {
            for(int i = 0; i < 5000;i++ ){
                atomicInt.incrementAndGet();
            }
        });
        Thread t2 = new Thread(() -> {
            for(int i = 0; i < 5000;i++ ){
                atomicInt.incrementAndGet();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(atomicInt.get());//获取的是10000
    }
}

引用类型原子类:AtomicStampedReference:用于原子操作的对象引用,同时带有版本号。

public class AtomicStampedReferenceDemo1 {
    public static void main(String[] args) {
        AtomicStampedReference<String> ref = new AtomicStampedReference<>("Hello", 0);
        ref.compareAndSet("Hello", "World",
                0, 1); // 更新引用和版本号
        System.out.println(ref.getReference());//expectedStamp和initialStamp相等,
                                               // 则更新initialRef引用值为newReference,并且更新版本号
    }
}

compareAndSet 方法的作用:

  • 检查当前引用值是否为 “Hello”。
  • 检查当前版本号是否为 0。
  • 如果两个条件都满足,则将引用值更新为 “World”,版本号更新为 1
    在这里插入图片描述
    数组类型原子类:AtomicReferenceArray:用于原子操作对象引用数组。
public class AtomicReferenceArrayDemo {
    public static void main(String[] args) {
        AtomicReferenceArray<String> array = new AtomicReferenceArray<>(new String[]{"Hello", "World"});
        array.set(1, "Java");//将索引为1的引用改为Java
        System.out.println(array.get(1));
    }
}

1.4 线程池

线程池

1.5 信号量 Semaphore

Semaphore 的核心思想是通过一组许可证(permits)来控制对资源的访问。每个线程在访问资源之前,必须先获取一个许可证;访问完成后,释放许可证。许可证的数量是有限的,当许可证用完时,后续的线程将被阻塞,直到有许可证被释放。

代码实例
public class SemaphoreDemo {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(5);
        System.out.println("使用第一个许可证");
        semaphore.acquire();
        System.out.println("使用第二个许可证");
        semaphore.acquire();
        System.out.println("使用第三个许可证");
        semaphore.acquire();
        System.out.println("使用第四个许可证");
        semaphore.acquire();
//        semaphore.release();
        semaphore.acquire();
        System.out.println("使用第五个许可证");
    }
}

在这里插入图片描述
将许可证改为4张,任务还是5个:
在这里插入图片描述
这里可以通过jconsole.exe来调试看下运行结果:
在这里插入图片描述

还是四张许可证,但是这里释放了一张许可证:
在这里插入图片描述

1.6 CountDownLatch

使用多线程,经常将一个大的任务分成多个子任务,使用多线程执行子任务,提高执行效率。

怎么判断子任务全部执行完毕呢?
这里就可以用CountDownLatch来记录各个任务完成。

  1. 构造 CountDownLatch 实例, 初始化 10 表⽰有 10 个任务需要完成.
  2. 每个任务执⾏完毕, 都调⽤ latch.countDown() . 在 CountDownLatch 内部的计数器同时⾃减.
  3. 主线程中使⽤ latch.await(); 阻塞等待所有任务执⾏完毕. 相当于计数器为 0 了
代码实例
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        Thread t1 = new Thread(()->{
            for(int i=0;i<3;i++){
                try {
                    Thread.sleep((long) (Math.random() * 2000));
                    latch.countDown();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        latch.await(); // 阻塞主线程,直到计数器为 0
        System.out.println("所有任务执行完毕");
    }
}

1.7 线程安全的集合类

Vector, Stack, HashTable, 是线程安全的(不建议⽤), 其他的集合类不是线程安全的

多线程环境使⽤ ArrayList

让ArrayList变成线程安全:

  1. ⾃⼰使⽤同步机制 (synchronized 或者 ReentrantLock)
  2. Collections.synchronizedList(new ArrayList);
    返回List的各种关键方法都带synchronized,这种做法类似于Vector, Stack
  3. 使⽤ CopyOnWriteArrayList
    读操作:读操作直接访问底层数组,不需要加锁,因此性能很高。
    写操作:
  • 创建底层数组的完整副本。
  • 在副本上进行修改操作。
  • 将副本替换为原始数组。
    这种操作的效率相对低效,因为每次都需要复制整个数组。
多线程环境使⽤哈希表

HashMap 本⾝不是线程安全的.
在多线程环境下使⽤哈希表可以使⽤:
• Hashtable
• ConcurrentHashMap

Hashtable
  • 使用全局锁(synchronized)保护整个哈希表(这意味着在任何时刻,只有一个线程可以修改哈希表,其他线程必须等待),所有操作(包括读写)都会锁住整个表。
  • 这种机制简单但效率低下,尤其是在高并发场景下,容易导致线程阻塞。

存在缺点:

  1. 如果多线程访问同⼀个 Hashtable 就会直接造成锁冲突.
  2. size 属性也是通过 synchronized 来控制同步, 也是⽐较慢的.
  3. ⼀旦触发扩容, 就由该线程完成整个扩容过程. 这个过程会涉及到⼤量的元素拷⻉, 效率会⾮常低.
ConcurrentHashMap
  • 使用分段锁(Segment)机制,将哈希表分为多个段,每个段有自己的锁。
  • JDK 1.8 以后,进一步优化为基于 CAS 和 synchronized 的锁机制,结合数组 + 链表 + 红黑树的数据结构。
  • 读操作通常不需要加锁,写操作的锁粒度更细,大大减少了锁竞争。

优化扩容:

  1. 发现需要扩容的线程, 只需要创建⼀个新的数组, 同时只搬⼏个元素过去.
  2. 扩容期间, 新⽼数组同时存在.
  3. 后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程. 每个操作负责搬运⼀⼩部分元素.
  4. 搬完最后⼀个元素再把⽼数组删掉.
  5. 这个期间, 插⼊只往新数组加.
  6. 这个期间, 查找需要同时查新数组和⽼数组

1.8 死锁

线程安全

此篇博客的全部代码!!!

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

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

相关文章

SpringBoot 如何调用 WebService 接口

前言 调用WebService接口的方式有很多&#xff0c;今天记录一下&#xff0c;使用 Spring Web Services 调用 SOAP WebService接口 一.导入依赖 <!-- Spring Boot Web依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId…

从Manus到OpenManus:多智能体协作框架如何重构AI生产力?

文章目录 Manus&#xff1a;封闭生态下的通用AI智能体OpenManus&#xff1a;开源社区的闪速复刻挑战与未来&#xff1a;框架落地的现实边界当前局限性未来演进方向 OpenManus使用指南1. 环境配置2. 参数配置3. 替换搜索引擎4. 运行效果 协作框架开启AI生产力革命 Manus&#xf…

1.5 双指针专题:有效三⻆形的个数(medium)

1.题目链接 611. 有效三角形的个数 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/valid-triangle-number/submissions/609232447/ 2.题目描述 给定⼀个包含⾮负整数的数组 nums &#xff0c;返回其中可以组成三⻆形三条边的三元组个数。 ⽰例 1: 输…

大数据学习(59)-DataX执行机制

&&大数据学习&& &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 承认自己的无知&#xff0c;乃是开启智慧的大门 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主哦&#x1f91…

《面向对象程序设计-C++》实验一 熟悉Visual C++开发环境及上机过程

一、实验目的 了解和使用VC集成开发环境&#xff1b;熟悉VC环境的基本命令和功能键&#xff1b;熟悉常用的功能菜单命令&#xff1b;学习使用VC环境的帮助&#xff1b;学习完整的C程序开发过程&#xff1b;理解简单的C程序结构。 二、实验内容 使用Visual C 6.0集成环境来编…

Chebykan wx 文章阅读

文献筛选 [1] 神经网络&#xff1a;全面基础 [2] 通过sigmoid函数的超层叠近似 [3] 多层前馈网络是通用近似器 [5] 注意力是你所需要的 [6] 深度残差学习用于图像识别 [7] 视觉化神经网络的损失景观 [8] 牙齿模具点云补全通过数据增强和混合RL-GAN [9] 强化学习&#xff1a;一…

2025解决软件供应链安全,开源安全的版本答案:SCA+SBOM

GitHub&#xff1a; https://github.com/XmirrorSecurity/OpenSCA-cli/ Gitee&#xff1a; https://gitee.com/XmirrorSecurity/OpenSCA-cli/ OpenSCA官网&#xff1a; https://opensca.xmirror.cn/ 根据Sonatype 发布的《软件供应链现状》报告&#xff0c;其中强调软件供…

Linux 系统负载过高的排查思路

技术探讨&#xff1a;Linux系统负载过高的排查思路 在Linux服务器运行过程中&#xff0c;如果系统负载过高&#xff0c;可能会导致性能下降和服务不稳定。以下是针对Linux系统负载过高问题的排查思路和解决方法&#xff1a; 1. 查看系统负载&#xff1a; 使用uptime或top命令查…

typora高亮方案+鼠标侧键一键改色

引言 在typora里面有一个自定义的高亮, <mark></mark>>但是单一颜色就太难看了, 我使用人工智能, 搜索全网艺术家, 汇集了几种好看的格式,并且方便大家侧键一键 调用, 是不是太方便啦 ! 示例 午夜模式 春意盎然 深海蓝调 石墨文档 秋日暖阳 蜜桃宣言 使用方法 …

自然语言处理文本分析:从词袋模型到认知智能的进化之旅

清晨&#xff0c;当智能音箱准确识别出"播放周杰伦最新专辑"的模糊语音指令时&#xff1b;午间&#xff0c;企业舆情系统自动标记出十万条评论中的负面情绪&#xff1b;深夜&#xff0c;科研人员用GPT-4解析百万篇论文发现新材料线索——这些场景背后&#xff0c;是自…

基于SSM+Vue+uniapp的考研交流(带商城)小程序+LW示例参考

系列文章目录 1.基于SSM的洗衣房管理系统原生微信小程序LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统LW参考示例 3.基于SpringBootVue的企业人事管理系统LW参考示例 4.基于SSM的高校实验室管理系统LW参考示例 5.基于SpringBoot的二手数码回收系统原生微信小程序LW参考示…

浙江大学:DeepSeek行业应用案例集(153页)(文末可下载PDF)

浙江大学&#xff1a;DeepSeek行业应用案例集&#xff08;153页&#xff09;&#xff08;文末可下载PDF&#xff09; 全文链接&#xff1a;浙江大学&#xff1a;DeepSeek行业应用案例集&#xff08;153页&#xff09;&#xff08;文末可下载PDF&#xff09; | AI探金 全文链接&…

深度学习分类回归(衣帽数据集)

一、步骤 1 加载数据集fashion_minst 2 搭建class NeuralNetwork模型 3 设置损失函数&#xff0c;优化器 4 编写评估函数 5 编写训练函数 6 开始训练 7 绘制损失&#xff0c;准确率曲线 二、代码 导包&#xff0c;打印版本号&#xff1a; import matplotlib as mpl im…

共享经济时代下,鲲鹏共享科技如何逆袭改命?

2016年&#xff0c;当共享充电宝顶着“资本泡沫”的质疑横空出世时&#xff0c;没人能想到&#xff0c;这个曾被王思聪嘲讽“能成我吃翔”的行业&#xff0c;竟在短短几年内成为共享经济领域最顽强的幸存者。数据显示&#xff0c;2019年共享充电宝用户规模突破3亿&#xff0c;单…

说一下spring的事务隔离级别?

大家好&#xff0c;我是锋哥。今天分享关于【说一下spring的事务隔离级别&#xff1f;】面试题。希望对大家有帮助&#xff1b; 说一下spring的事务隔离级别&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring的事务隔离级别是指在数据库事务管理中…

Web开发第五节

一.结构伪类选择器 &#xff08;一&#xff09;选择单个 &#xff08;二&#xff09;选择多个 注&#xff1a;1.n5指的是5以后的数字&#xff0c;包含5&#xff0c;n从0开始 2.-n5指的是5以前的数字&#xff0c;同样包含5&#xff0c;并且n从0开始 二.伪元素选择器 注&…

计算机毕业设计:驾校综合信息系统

驾校综合信息系统mysql数据库创建语句驾校综合信息系统oracle数据库创建语句驾校综合信息系统sqlserver数据库创建语句驾校综合信息系统springspringMVChibernate框架对象(javaBean,pojo)设计驾校综合信息系统springspringMVCmybatis框架对象(javaBean,pojo)设计 驾校综合信息系…

无标签数据增强+高效注意力GAN:基于CARLA的夜间车辆检测精度跃升

目录 一、摘要 二、引言 三、框架 四、方法 生成合成夜间数据 昼夜图像风格转换 针对夜间图像的无标签数据增强技术 五、Coovally AI模型训练与应用平台 六、实验 数据 图像风格转换 夜间车辆检测和分类 结论 论文题目&#xff1a;ENHANCING NIGHTTIME VEHICLE D…

RocketMQ面试题:原理部分

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

NAFNet:Simple Baselines for Image Restoration

Abstract 近年来&#xff0c;图像复原技术取得了长足的进步&#xff0c;但现有的图像复原方法&#xff08;SOTA&#xff09;系统复杂度也在不断增加&#xff0c;不利于对各种方法的分析和比较。在本文中&#xff0c;我们提出了一种简单的基线&#xff0c;它超越了SOTA方法&…