【JavaEE】多线程(7)

news2025/1/10 20:23:06

一、JUC的常见类

JUC→java.util.concurrent,放了和多线程相关的组件

1.1 Callable 接口

看以下从计算从1加到1000的代码:

public class Demo {
    public static int sum;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                int result = 0;
                for (int i = 1; i <= 1000; i++) {
                    result += i;
                }
                sum = result;
            }
        });
        
        t.start();
        t.join();

        System.out.println(sum);
    }
}

通过成员变量的方式才能得到线程中任务的最终执行结果,这样会增加代码的耦合性,当改变外部的sum,线程内的sum也要改,所以引入Callable接口

public class Demo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int result = 0;
                for (int i = 1; i <= 1000; i++) {
                    result += i;
                }
                return result;
            }
        };

        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
        t.join();

        System.out.println(futureTask.get());
    }
}

总结:

  1. Callable接口也用来描述一个任务,其中的call方法可以设置返回值将任务执行结果返回
  2. Runnable接口可以用来描述一个任务,但run方法的返回类型为void,无法返回任务结果
  3. callable需要经过FutureTask封装一下才能传入Thread构造方法,否则编译报错

至此创建线程有5种方式:

  1. 继承Thread
  2. 使用Runnable
  3. 使用lambda表达式
  4. 使用线程池/ThreadFactory
  5. 使用Callable接口

2.2 ReentrantLock

ReentrantLock 是可重入锁,它将加锁和解锁两个操作进行了区分,提供了加锁:lock()方法 和解锁:unlock()方法

public class Demo {
    public static void main(String[] args) {
        ReentrantLock locker = new ReentrantLock();
        locker.lock();
        locker.unlock();
    }
}

其实这样并不好,因为在加锁和解锁之间可能会存在很多逻辑,假如在解锁之前就通过return返回了,或者抛出了异常,这样走不到解锁,所以一般将unlock写到finally中,finally中的代码无论如何都会被执行

public class Demo {
    public static void main(String[] args) {
        ReentrantLock locker = new ReentrantLock();
        try {
            locker.lock();
        } finally {
            locker.unlock();
        }
    }
}

synchronized也是可重入锁,那ReentrantLock相比于synchronized有哪些其他价值?

  1. ReentrantLock 提供了公平锁,向ReentrantLock的构造方法里传入true,就是公平锁,ReentrantLock locker = new ReentrantLock(true);
  2. ReentrantLock提供了tryLock()操作,当尝试加锁,如果所以经被获取了,则返回操作失败,tryLock()还提供了带时间参数版本,可以等待一定时间,时间到了再返回操作失败
  3. synchronized搭配wait notify等待通知机制,ReentrantLock搭配Condition类完成等待通知,Condition可以指定线程唤醒

1.3 信号量 Semaphore

信号量就是一个计数器,描述了可用资源的个数,有两个核心操作:

  • P操作:计数器 -1,申请资源→acquire()
  • V操作:计数器 +1,释放资源→release()
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(3);
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire(); //最多申请3次
        //semaphore.acquire(); // 这里会进行阻塞, 直到有线程执行release为止

        semaphore.release(); //释放一个后, 就不会阻塞了
        semaphore.acquire();
    }
}

将信号量设为1,其作用就等价于一个锁,当一个线程申请资源后,另外一个线程再申请,就会进入阻塞,直到第一个线程释放资源

public class Demo {

    public static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(1);
        Thread t1 = new Thread(() -> {
            try {
                for (int i = 0; i < 50000; i++) {
                    semaphore.acquire();
                    count++;
                    semaphore.release();
                }

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                for (int i = 0; i < 50000; i++) {
                    semaphore.acquire();
                    count++;
                    semaphore.release();
                }

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

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("count = "+ count);
    }
}

1.4 CountDownLatch

当我们把一个任务拆分成多个的时候,当所有的任务(线程)都执行完后将结果合并,此时就可以使用这个工具类来识别所有线程是否都执行完了

public class Demo18 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(10); // 配合下面的countDown 和 await
        for (int i = 0; i < 10; i++) {
            int id = i;
            Thread t = new Thread(() -> {
                System.out.println("线程开始 "+ id);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程结束 "+ id);
                latch.countDown(); //每个线程执行完都执行一次countDown
            });
            t.start();
        }

        latch.await(); // 所有线程都执行完countDown 后再往下执行,否则这里阻塞等待

        System.out.println("所有线程执行结束");

    }
}

二、集合类的线程安全问题

Vector、Stack、HashTable这三个集合类是线程安全的,因为它们的方法都被synchronized修饰

2.1 多线程使用ArrayList

1)自己使用线程同步机制,加上synchronized或ReentrantLock

2)使用Collections.synchronizedList (new ArrayList);synchronizedList是一个基于synchronized的List,synchronizedList的关键操作上都带有synchronized

3)使用CopyOnWriteArrayList——写时拷贝;当我们向一个容器中插入元素时,会将原容器拷贝出一份新容器,往新容器里插入元素,插入完毕后,将原容器的引用指向新容器

写时拷贝的好处:解决线程安全问题,当在修改时,许多线程也在读,通过写时拷贝的处理,让线程只能在原容器读,在新容器写,写完后,原容器的引用指向新容器

2.2 多线程使用哈希表

HashMap 本身是线程不安全的,在多线程环境下可以使用:Hashtable ConcurrentHashMap

1)Hashtable

在一些关键操作上加上synchronized,这相当于直接针对Hashtable对象本身加锁

  • 多线程访问同一个Hashtable就会造成锁冲突,一个线程使用Hashtable中的关键方法时都会使其他要使用Hashtable的线程进入阻塞
  • Hashtable 一旦触发扩容,就让触发扩容的线程完成整个扩容,效率非常低,因为涉及到大量元素的拷贝

2)ConcurrentHashMap

 ConcurrentHashMap做了一些优化

1)在加锁方式上,使用"锁桶"的方式来代替一把"全局锁",ConcurrentHashMap在哈希表中的每一个链表都加上了锁

这样当两个不同的线程操作不同的链表的时候不会产生锁冲突,这样加了锁而且链表的个数通常很多,大部分情况都不会产生锁冲突,synchronized不产生锁冲突就是个偏向锁

2)size是真个哈希表的属性,不同的线程在执行插入或删除元素时会涉及到多个线程修改同一个变量,所以采用CAS的方式修改size,避免了加锁操作

【注意】这里不要和Hashtable搞混,Hashtable是一把全局锁,一个线程像哈希表中插入元素时,其他线程都在阻塞等待,直到插入完,所以不会涉及到多个线程修改size的情况;但ConcurrentHashMap 是锁桶的方式加锁,针对每个链表加锁,所以会有多个线程同一个时间段插入的情况,那么多个线程修改size的情况就不可避免,因此这里采用CAS的方式修改size

3)ConcurrentHashMap 针对扩容操作做了特殊优化,Hashtable是在触发扩容时,直接在那个线程的put方法里完成整个扩容,而ConcurrentHashMap在扩容会搞一个新数组,这个数组的容量是扩容后的容量

接下来要把旧数组中的数据拷贝到新数组中,但并不是一次性拷贝完,而是在之后,每次有线程进行哈希表的基本操作时,都会把一部分数据从旧数组搬运到新数组

在搬运的过程中:

  • 插入:在新的数组插入
  • 删除:新旧数组都要删除
  • 查找:新就数组都要查找

🙉到此,多线程的篇章全部结束

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

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

相关文章

宝塔面板-java项目 spring 无法正常启动 java spring 宝塔 没有显示日志 问题解决方案-spring项目宝塔面板无日志

宝塔面板-java项目 spring 无法正常启动 java spring 宝塔 没有显示日志 -优雅草央千澈问题解决方案-spring项目宝塔面板无日志 问题描述 昨天安排了一个新项目的开发&#xff0c;搭建兄弟搭建完但是通信有问题&#xff0c;spring服务无法正常启动&#xff0c;于是交代后端兄…

关于一些游戏需要转区的方法

当玩非国区游戏时有时会出现乱码导致无法启动&#xff0c;此时多半需要转区来进行解决 1.下载转区软件 【转区工具】Locale Emulator 下载链接&#xff1a;Locale.Emulator.2.5.0.1.zip - 蓝奏云 用此软件可以解决大部分问题。 2.进行系统转区 首先打开控制面板选择时间与…

浅谈网络 | 应用层之云网络隔离GRE/VXLAN

目录 前言GRE 隧道技术VXLANGRE/VXLAN接入云平台 前言 之前提到&#xff0c;为云平台中的租户实现隔离时&#xff0c;常用的策略是基于 VLAN。然而&#xff0c;VLAN 只有 12 位&#xff0c;共支持 4096 个 ID&#xff0c;这在最初设计时看似足够&#xff0c;但随着云计算的快速…

【Python】批量下载抖音视频

1、代码 import os import re from concurrent.futures import ThreadPoolExecutor import requestsdef get_urls(max_cursor):# 请求头 &#xff08;页面获取&#xff09;headers {Cookie: ,Referer: ,User-Agent: }# 请求地址&#xff08;页面获取&#xff09;url # max_c…

刚入行Java,如何深入学习JVM底层原理?

对于JVM&#xff0c;我想大部分小伙伴都是要面试了才会去学&#xff0c;其余时间基本不会去看&#xff08;掐指一算&#xff0c;你们书架上面的深入理解Java虚拟机第三版应该都一层灰了吧【手动狗头】&#xff09;。但值得一说的是&#xff0c;当你工作多年之后&#xff0c;你遇…

【Redis】深入解析Redis缓存机制:全面掌握缓存更新、穿透、雪崩与击穿的终极指南

文章目录 一、Redis缓存机制概述1.1 Redis缓存的基本原理1.2 常见的Redis缓存应用场景 二、缓存更新机制2.1 缓存更新的策略2.2 示例代码&#xff1a;主动更新缓存 三、缓存穿透3.1 缓存穿透的原因3.2 缓解缓存穿透的方法3.3 示例代码&#xff1a;使用布隆过滤器 四、缓存雪崩4…

java中的数组(2)

大家好&#xff0c;我们今天继续来看java中数组这方面的知识点&#xff0c;那么话不多说&#xff0c;我们直接开始。 一.数组的使用 1.数组中元素访问 数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,数组可以通过下标访问其任意位置的元素. 也可以进行修改…

Qt入门7——Qt事件

目录 1. Qt事件介绍&#xff1a; 2. 事件的处理 示例1&#xff1a;鼠标进入(enterEvent)与离开事件(leaveEvent) 示例2&#xff1a;鼠标点击事件(mousePressEvent) 示例3&#xff1a;鼠标移动事件(mouseMoveEvent) 3. 按键事件 4. 定时器 5. 窗口事件 1. Qt事件介绍&a…

PyQt事件机制练习

一、思维导图 二、代码 import sysfrom PyQt6.QtTextToSpeech import QTextToSpeech from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QLineEdit from PyQt6 import uic from PyQt6.QtCore import Qt, QTimerEvent, QTimeclass MyWidget(QWidget):d…

【河南】《关于省级政务信息化建设项目支出预算标准的规定(试行)》(豫财预〔2020〕81号)-省市费用标准解读系列25

《关于省级政务信息化建设项目支出预算标准的规定(试行)》&#xff08;豫财预 〔2020〕81号&#xff09;是河南省财政厅2020年8月27日发布的信息化项目预算标准&#xff08;了解更多可直接关注我们咨询&#xff09;。该标准旨在加强河南省省级部门预算管理&#xff0c;规范省级…

oscp备考,oscp系列——Kioptix Level 3靶场

Kioptix Level 3 oscp备考&#xff0c;oscp系列——Kioptix Level 3靶场 nmap扫描 主机发现 └─# nmap -sn 192.168.80.0/24 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-09 00:33 CST Nmap scan report for 192.168.80.1 Host is up (0.00014s latency). MAC…

活动|华院计算董事长宣晓华应邀出席2024科创大会并作圆桌嘉宾

2024科创大会在上海举行&#xff0c;由中央广播电视总台和上海市人民政府共同主办。本次大会以“创新驱动 新质未来”为主题&#xff0c;来自知名院校、科研机构的专家学者以及科技企业、金融机构的相关负责人共聚一堂&#xff0c;探讨人工智能、生物医药等产业应用前景&#x…

Robust Univariate Mean Estimation算法简介

Robust Univariate Mean Estimation 是一种统计算法&#xff0c;主要用于在单变量场景中估计样本的均值&#xff0c;同时对异常值&#xff08;outliers&#xff09;具有鲁棒性。传统的均值估计使用样本的算术平均值&#xff0c;但它对异常值高度敏感。为了缓解这个问题&#xf…

C/C++流星雨

系列文章 序号直达链接1C/C爱心代码2C/C跳动的爱心3C/C李峋同款跳动的爱心代码4C/C满屏飘字表白代码5C/C大雪纷飞代码6C/C烟花代码7C/C黑客帝国同款字母雨8C/C樱花树代码9C/C奥特曼代码10C/C精美圣诞树11C/C俄罗斯方块12C/C贪吃蛇13C/C孤单又灿烂的神-鬼怪14C/C闪烁的爱心15C/C…

MySQL——buffer poll

为什么要有buffer poll&#xff1f; 如果没有buffer poll&#xff0c;每次读取数据的时候都是从磁盘上读的&#xff0c;这样效率是很差的的。 所以有了提高效率的方式&#xff0c;就加上了一个缓存——buffer poll 所以&#xff0c;当我们读取数据的时候就有以下的方式 当读…

2025年山东省职业院校技能大赛“信息安全管理与评估”(山东省) 任务书

2025年山东省职业院校技能大赛“信息安全管理与评估”(山东省 任务书 模块一网络平台搭建与设备安全防护任务1&#xff1a;网络平台搭建 &#xff08;50分&#xff09;任务2&#xff1a;网络安全设备配置与防护&#xff08;250分&#xff09; 模块二网络安全事件响应、数字取证…

ERROR: KeeperErrorCode = NoNode for /hbase/master

原因分析 通过上面的情景模拟&#xff0c;我们可以看到报错的原因在于zookeeper中出现问题&#xff0c;可能是zookeeper中的/hbase/master被删除&#xff0c;或者是在hbase集群启动之后重新安装了zookeeper&#xff0c;导致zookeeper中的/hbase/master节点数据异常。 1. 停止…

ElasticSearch7.8学习笔记

1. ElasticSearch概述 1.1 ElasticSearch是什么 Elaticsearch&#xff0c;简称为 ES&#xff0c;ES 是一个开源的高扩展的分布式全文搜索引擎&#xff0c;是整个Elastic Stack 技术栈的核心。它可以近乎实时的存储、检索数据&#xff1b;本身扩展性很好&#xff0c;可以扩展到…

CSS制作字体炫彩效果

目录 序言一、思路二、代码实现 序言 本节内容主要来讲解一下炫彩字体的实现&#xff0c;主要通过使用 background-clip以及background-position来实现&#xff0c;那么话不多说开始本节内容的学习吧&#xff01; 一、思路 添加盒子以及内容使用 background-clip属性&#x…

如何防止短信盗刷和短信轰炸?

短信盗刷和短信轰炸是项目开发中必须要解决的问题之一&#xff0c;它的优先级不亚于 SQL 注入的问题&#xff0c;同时它也是面试中比较常见的一个经典面试题&#xff0c;今天我们就来看下&#xff0c;如何防止这个问题。 1.概念介绍 短信盗刷和短信轰炸的概念如下&#xff1a…