JUC基础-0531

news2025/2/26 3:40:19

3 线程间通信

线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析

多线程编程步骤

  1. 第一步:创建资源类,在资源类创建属性和操作方法
  2. 第二步:在资源类操作方法
    1. 判断
    2. 干活
    3. 通知
  3. 第三步:创建多个线程,调用资源类的操作方法
  4. 第四步:防止虚假唤醒问题

周阳四大口诀

  1. 高内聚低耦合前提下,封装思想 -> 线程操作 -> 资源类
  2. 判断、干活、通知
  3. 防止虚假唤醒,wait方法要注意
  4. 注意标志位flag,可能是volatile的

题目:场景两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信

//	资源类,
class Share2{

    private int number = 0;

    public synchronized void incr() throws InterruptedException {
        if(number != 0){
            wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+" : "+number);
        notifyAll();
    }

    public synchronized void decr() throws InterruptedException {

        if(number != 1){
            wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+" : "+number);
        notifyAll();
    }
}


public class ThreadDemo2 {

    public static void main(String[] args) {

        Share2 s = new Share2();

        new Thread(()->{
            try {
                while(true){
                    s.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"AA").start();


        new Thread(()->{
            try {
                while(true){
                    s.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"BB").start();
    }
}

执行:可以完成需求

存在问题:当我把线程增加到4个,2个加,2个减,那么就会出现结果变成0或1以外的值。

虚假唤醒问题

本质:唤醒后依然需要判断条件

对于条件的判断需要一直在while循环里面,把判断放进if就很容易出现问题。

因为wait在那里睡,在哪里醒,醒了之后继续执行后面的代码,有可能造成判断失效

使用while包裹起来,wait醒来之后还是会进行判断条件,只有等待条件不满足,不会进行wait,才会继续执行。

使用lock完成刚才的场景,使用newCondition()方法获取condition对象,使用await方法和signal方法。

//	Lock版本
class MyShare{

    private int number = 0;

    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void incr()  {
        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 decr()  {

        lock.lock();

        try {

            while (number!=1){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+" : "+number);
            condition.signalAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }

    }

}
public class MyThreadDemo2 {

    public static void main(String[] args) {

        MyShare myShare = new MyShare();

        new Thread(()->{
            for(int i=0;i<40;i++){
                myShare.incr();
            }
        },"AA").start();

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

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

        new Thread(()->{
            for(int i=0;i<40;i++){
                myShare.decr();
            }
        },"DD").start();
    }
}

4 线程间定制化通信

主要内容:使用Lock接口里面的newCondition创建Condition对象,使用condition对象进行特定唤醒和睡眠达成效果

问题:A线程打印5次A,B线程打印 10 次 B,C线程打印15次C,按照此顺序循环10轮 主要在于按照顺序执行

请添加图片描述

class MyShare3{

    //  1 AA   2 BB  3 CC
    private int flag = 1;
    private Lock lock =  new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void print5(int loop){

        lock.lock();
        try {
            while (flag!=1){
                condition1.await();
            }
            for(int i=1;i<=5;i++){
                System.out.println(loop+" 轮 :"+Thread.currentThread().getName()+" : "+i);
            }
            flag = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }


    public void print10(int loop){

        lock.lock();
        try {
            while (flag!=2){
                condition2.await();
            }
            for(int i=1;i<=10;i++){
                System.out.println(loop+" 轮 :"+Thread.currentThread().getName()+" : "+i);
            }
            flag = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public void print15(int loop){

        lock.lock();
        try {
            while (flag!=3){
                condition3.await();
            }
            for(int i=1;i<=15;i++){
                System.out.println(loop+" 轮 :"+Thread.currentThread().getName()+" : "+i);
            }
            flag = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}

public class MyThreadDemo3 {

    public static void main(String[] args) {

        MyShare3 share3 = new MyShare3();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                share3.print5(i);
            }
        },"AA").start();

        new Thread(()->{
            for(int i=1;i<=10;i++){
                share3.print10(i);
            }
        },"BB").start();

        new Thread(()->{
            for(int i=1;i<=10;i++){
                share3.print15(i);
            }
        },"CC").start();
    }
}

5 集合的线程安全

5.1 ArrayList线程不安全演示

多线程对集合修改,造成 java.util.ConcurrentModificationException 异常

public class NotSafeDemo {
    /**
     * 多个线程同时对集合进行修改 * @param args
     */
    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i = 0; i < 100; i++) { new Thread(() ->{
            list.add(UUID.randomUUID().toString()); System.out.println(list);
        }, "线程" + i).start(); }
    }
}

异常内容
java.util.ConcurrentModificationException
问题:为什么会出现并发修改异常?
查看 ArrayList 的 add 方法源码 : add方法没有使用synchronized修饰

接下来展示几种解决方案:

5.1.2 Vector

使用Vector替换ArrayList:

List<String> list = new Vector<>();

实际使用的并不多,这个是JDK1.0出现的,并且Vector存在很多问题:

  1. 同步开销:由于Vector的每个方法都是同步的,即使在单线程环境下,这也会引入额外的同步开销。这使得 Vector 在性能方面可能不如非线程安全的集合类,例如 ArrayList
  2. 高并发性能:对于高并发环境,Vector 的同步机制可能会导致性能瓶颈。在并发访问频繁的情况下,使用更加高效的并发集合类,如 ConcurrentLinkedQueueConcurrentHashMap,可以更好地满足并发性能需求。
  3. 更灵活的同步控制:虽然 Vector 的每个方法都是同步的,但在某些情况下,我们可能需要更精细的同步控制。使用 Collections.synchronizedList() 方法可以将非线程安全的集合类包装成线程安全的集合类,同时可以使用更细粒度的同步控制,从而获得更好的性能。

5.1.3 Collections

使用Collections里面的synchronizedList静态方法,传入new的集合对象就可以确保其线程安全:

List<String> list = Collections.synchronizedList(new ArrayList<>());

这个解决方案也比较古老

5.1.4 CopyOnWriteArrayList(重点)

通过JUC工具包里面的CopyOnWriteArrayList类解决:

也是一个用于替换ArrayList<>集合的类

List<String> list = new CopyOnWriteArrayList<>();

底层原理:写时复制技术

支持并发读,独立写:

请添加图片描述

即,需要写的时候,复制一份出来,在新内容里面进行写,写完之后再去合并两部分内容。

//	对写加锁
public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

代码实现里面,仅对写操作进行加锁,对于读操作,类似indexOf的操作,并不进行加锁操作。

原因分析(重点):动态数组与线程安全

下面从“动态数组”和“线程安全”两个方面进一步对 CopyOnWriteArrayList 的原理进行说明。

  • “动态数组”机制

    • 它内部有个“volatile 数组”(array)来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile 数组”, 这就是它叫做 CopyOnWriteArrayList 的原因
    • 由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的 操作,CopyOnWriteArrayList效率很低;但是单单只是进行遍历查找的话, 效率比较高。**
  • “线程安全”机制

    • 通过 volatile 和互斥锁来实现的。
    • 通过“volatile 数组”来保存数据的。一个线程读取 volatile 数组时,总能看到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读取到的数据总是最新的”这个机制的保证。
    • 通过互斥锁来保护数据。在“添加/修改/删除”数据时,会先“获取互斥锁”,再修改完毕之后,先将数据更新到“volatile 数组”中,然后再“释放互斥 锁”,就达到了保护数据的目的。

5.2 HashSet线程不安全

线程不安全演示:

Set<String> set = new HashSet<>();
    for (int i = 0; i <30; i++) {
          new Thread(()->{
             //向集合添加内容
             set.add(UUID.randomUUID().toString().substring(0,8));
             //从集合获取内容
             System.out.println(set);
          },String.valueOf(i)).start();
    }

HashSet的add方法没有加上synchronized关键字

解决方案:

5.2.1 CopyOnWriteArraySet

Set<String> set = new CopyOnWriteArraySet<>();

5.3 HashMap线程不安全

HashMap的put方法没有加上synchronized关键字

Map<String,String> map = new HashMap<>();

      for (int i = 0; i <30; i++) {
           String key = String.valueOf(i);
           new Thread(()->{
                //向集合添加内容
              map.put(key,UUID.randomUUID().toString().substring(0,8));
                //从集合获取内容
              System.out.println(map);
           },String.valueOf(i)).start();
        }
    }

5.3.1 ConcurrentHashMap

Map<String,String> map = new ConcurrentHashMap<>();

ConCurrentHashMap如何实现线程安全:

  1. 分段锁:ConcurrentHashMap 内部使用了分段锁(Segment),将哈希表分成多个段(Segment),每个段上有一个独立的锁。不同的线程可以同时访问不同的段,从而实现更高的并发性。
  2. 原子操作:ConcurrentHashMap 使用了一些原子操作(Atomic Operations),例如 compareAndSetvolatile 关键字,来确保在并发修改时的数据一致性。
  3. 安全发布机制:ConcurrentHashMap 在创建时会进行一些安全发布机制的操作,确保其他线程在完全构造之前无法访问它。
  4. 无阻塞算法:ConcurrentHashMap 在并发修改时使用了无阻塞算法(Lock-Free),这意味着即使在高并发情况下,线程不会被阻塞在锁上,从而提高了并发性能。
public V put(K key, V value) {return putVal(key, value, false);}

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K, V>[] tab = table;;) {
        Node<K, V> f;
        int n, i, fh;
        // 1. 分段锁
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 2. 原子操作
            // 如果节点为空,在对应位置上使用 CAS 操作进行插入
            if (casTabAt(tab, i, null,
                new Node<K, V>(hash, key, value, null)))
                break; // no lock when adding to empty bin
        } else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            // 3. 同步控制
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K, V> e = f;; ++binCount) {
                            K ek;
                            // 遍历链表查找键是否已存在
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                    (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K, V> pred = e;
                            // 到达链表尾部,将新节点插入链表末尾
                            if ((e = e.next) == null) {
                                pred.next = new Node<K, V>(hash, key, value, null);
                                break;
                            }
                        }
                    } else if (f instanceof TreeBin) {
                        Node<K, V> p;
                        binCount = 2;
                        // 当前节点为树节点,调用树节点的插入操作
                        if ((p = ((TreeBin<K, V>) f).putTreeVal(hash, key, value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                // 4. 无阻塞算法
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

5.4 小结

1.线程安全与线程不安全集合

集合类型中存在线程安全与线程不安全的两种,常见例如:

ArrayList ----- Vector
HashMap -----HashTable
但是以上都是通过 synchronized 关键字实现,效率较低

2.Collections 构建的线程安全集合

3.java.util.concurrent 并发包下

CopyOnWriteArrayList

CopyOnWriteArraySet

ConCurrentHashMap

类型,通过动态数组与线程安全个方面保证线程安全

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

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

相关文章

电脑录音软件哪个好用?如何录制声音?

案例&#xff1a;有没有好用的电脑录音软件分享&#xff1f; 【使用手机录制电脑上的声音&#xff0c;会录入外界的杂音&#xff0c;导致录音文件质量不佳。我需要一款支持录制电脑声音的软件&#xff0c;小伙伴们有没有好用的电脑录音软件推荐&#xff1f;】 电脑录音软件成…

docker安装RabbitMQ教程(2023年最详细)

1.使用docker查询rabbitmq的镜像 docker search rabbitmq 2.安装镜像 如果需要安装其他版本在rabbitmq后面跟上版本号即可 docker pull rabbitmq:3.7.7-management 说明 docker pull rabbitmq:版本号 -management 安装name为rabbitmq的这里是直接安装最新的 docker pull …

算法leetcode|54. 螺旋矩阵(rust重拳出击)

文章目录 54. 螺旋矩阵&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a;每次循环移动一步&#xff1a;每次循环完成一个顺时针&#xff1a…

玩转 GPT4All

目录 什么是Chatgpt 什么是gpt4all 如何使用 第一步&#xff1a;下载LLM模型 第二步&#xff1a;下载代码 第三步&#xff1a;将模型替换到 第四步&#xff1a;执行启动命令 第五步&#xff1a;生成自己的客户端 第六步&#xff1a;启动 第七步&#xff1a;配置UI 什么…

【CesiumJS入门】(3)ImageryLayer之图层卷帘

前言 上一篇博客简单得介绍了影像图层并成功在视图上加载出来了&#xff0c;而今天我们来实现一个简单的可视化效果&#xff0c;影像图层卷帘。 前置知识&#xff1a;Cesium 事件详解&#xff08;鼠标事件、相机事件、键盘事件、场景触发事件&#xff09;_cesium点击事件_GIS…

OPT CST 慕藤光

OPT 波特率 数据长度 停止位 奇偶校验 9600 bps 8 bits 1 bit 无 所有通讯字节都采用ASCII码 特征字 &#xff1d; $命令字 &#xff1d; 1&#xff0c;2&#xff0c;3&#xff0c;4 打开对应通道电源关闭对应通道电源设置对应通道电源参数读出对应通道电…

【论文阅读】Twin Neural Network Regression

论文下载 GitHub bib: ARTICLE{SebastianKevin2022Twin,title {Twin neural network regression},author {Sebastian Johann Wetzel and Kevin Ryczko and Roger Gordon Melko and Isaac Tamblyn},journal {Applied AI Letters},year {2022},volume {3},number …

SpringBoot整合邮箱验证码实现用户注册

唠嗑部分 今天我们来分享一下在系统开发过程中&#xff0c;如何使用验证码来验证用户并完成用户注册 首先来看一下成品界面展示 说一下以上注册功能的设计&#xff1a; 用户手动输入用户名(全数据库唯一)、密码、确认密码、邮箱地址(单个邮箱最多可注册3个用户)、正确的邮箱…

Arm 推出 2023 全面计算解决方案,加速终端 AI 应用开发和部署

在当今数字化时代&#xff0c;人们对移动端计算能力的要求已经上升到了前所未有的高度。他们需要移动设备具有更快、更先进、更持久的计算能力&#xff0c;以提高生产力和生活质量。而科技厂商在满足人们对移动端计算能力的需求的同时&#xff0c;还需要从整个生态系统的角度出…

通过python封装接口seller_nick获取京东店铺所有商品数据,京东店铺所有商品数据接口,京东API接口

目的&#xff1a; 通过python封装接口seller_nick获取京东店铺所有商品数据&#xff0c;方法如下&#xff1a; 使用京东开放平台提供的API接口文档&#xff0c;找到seller_nick接口的具体参数及请求方式。 使用Python中的requests库发送请求&#xff0c;获取接口返回的数据。 …

nuxt3.0学习-三、nuxt.config.ts配置、跨域处理以及浏览器适配处理

nuxt官方对于nuxt.config.ts配置的介绍在Nuxt3.0 nuxt.config.ts配置 关于如何配置本人只能给出一点点启发&#xff0c;具体的配置需要根据个人需求去配置 nuxt.config.ts配置、跨域处理 import { prismjsPlugin } from "vite-plugin-prismjs"; export default de…

CompletableFuture异步和线程池

一、线程回顾 1、初始化线程的 4 种方式 1&#xff09;、继承 Thread 2&#xff09;、实现 Runnable 接口 3&#xff09;、实现 Callable 接口 FutureTask &#xff08;可以拿到返回结果&#xff0c;可以处理异常&#xff09; 4&#xff09;、线程池 方式 1 和方式 2&am…

《精通特征工程》学习笔记(1):数值特征处理

不进行多余的解释&#xff0c;想看原文直接下载pdf查看&#xff0c;本文是精简提炼了重要的方法写进来。 1.二值化 在百万歌曲数据集中&#xff0c;原始的收听次数并不是衡量用户喜好的强壮指标。&#xff08;在统计学术语 中&#xff0c;“强壮”意味着该方法适用于各种情况…

【2611. 老鼠和奶酪】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 有两只老鼠和 n 块不同类型的奶酪&#xff0c;每块奶酪都只能被其中一只老鼠吃掉。 下标为 i 处的奶酪被吃掉的得分为&#xff1a; 如果第一只老鼠吃掉&#xff0c;则得分为 reward1[i] 。如果第二…

Hive之HPLSQL安装手册

软件版本信息&#xff1a; CDH&#xff1a; cdh5.14.0 Hive: 1.1.0 Impala&#xff1a;2.11.0一&#xff1a;下载地址 Hplsql官网&#xff1a; http:www.hplsql.org/download 下载的是&#xff1a;hplsql-0.3.31.tar.gz版本 二&#xff1a;安装步骤 解压下载的hplsql-0.3.…

kafka 02

4.API开发 准备&#xff1a; 创建项目 &#xff0c; 添加依赖 XML <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <depen…

如何评价编程语言 Reason?

Reason编程语言的设计目标是为开发人员提供一种易于理解和学习的工具&#xff0c;同时提供静态类型检查和高效的代码执行。Reason基于OCaml语言&#xff0c;并引入了JavaScript的语法和工具链&#xff0c;使得开发者能够在现有的JavaScript生态系统中更好地开发和维护代码。Rea…

2023,数据库国产替代走到哪了?

如今&#xff0c;战场不仅银行&#xff0c;参战者也不仅单独的一家。对中国的国产数据库而言&#xff0c;机会和挑战都在加速涌来。 作者|思杭 编辑|皮爷 出品|产业家 2023&#xff0c;数据库格局正在变化&#xff0c;愈演愈烈。 如果说哪个环节是如今国产替代的最火热…

chatgpt赋能python:Python中如何输出两个数

Python中如何输出两个数 对于任何一种编程语言来说&#xff0c;输出两个数都是非常基础的知识点&#xff0c;Python也不例外。在Python中&#xff0c;我们可以使用print()函数来输出两个数。本篇文章将会介绍如何在Python中输出两个数。 介绍 在Python中&#xff0c;输出两个…

Python让文档处理变得轻松:如何快速替换Word文档中的关键字

应用场景&#xff1a; Python自动化处理Word文档的功能可以应用于许多场景&#xff0c;以下是其中一些常见的应用场景&#xff1a; 批量处理文档&#xff1a;如果您需要处理大量的Word文档&#xff0c;例如替换文本、添加文本、修改格式等&#xff0c;手动完成这些任务将非常耗…