多线程_进阶

news2024/9/23 23:34:31

文章目录

  • 线程通信
    • 概念
    • 使用方式
    • 案例
      • 单例模式
      • 阻塞式队列
      • 线程池
    • 常见的锁策略
      • 乐观锁 悲观锁
        • CAS
          • CAS存在的问题:ABA问题
      • 读写锁
      • 自旋锁
      • 公平锁 非公平锁
        • 非公平锁
        • 公平锁
      • synchronized
        • jvm对synchronized的优化:锁升级
        • synchronized的其他优化
      • Lock体系
        • synchronized vs lock
      • 独占锁vs共享锁
        • 独占锁
        • 共享锁
          • Semaphare:信号量
          • CountDownLatch
      • 死锁
  • 线程安全的集合
    • Map:重点
      • Hashtable
      • ConcurrentHashMap:并发的hashmap

线程通信

背景:多线程优势是提高cpu利用率,使用要注意:执行时间比较长的任务,可能存在线程安全问题
以上前提下,能否满足一种业务需求:需要让线程执行具有一定的顺序性

在这里插入图片描述

概念

线程间通信,就是一个线程以通知的方式,唤醒某些等待线程(也可以在某些条件下,让当前线程等待),这样就可以让线程间通过通信的方式满足一定的顺序性

使用方式

在这里插入图片描述
线程间通过通信的方式满足一定的顺序性:

在这里插入代码public class 简单使用 {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (lock) {
                        //先做一些事情
                        System.out.println("线程1:步骤1");
                        //在某些条件下,就需要等待
                        lock.wait();
                        //被唤醒,就做另一些事情
                        System.out.println("线程1:步骤2");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        /**
         * 执行顺序:
         * 线程1 synchronized -> wait
         * 线程2 synchronized -> notify
         * 线程1 wait 往下
         */
        Thread.sleep(100);
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    //做一些事情
                    System.out.println("线程2:步骤1");
                    //通知线程1:唤醒执行
                    lock.notify();
                    //做另一些事情
                    System.out.println("线程2:步骤2");
                }
            }
        }).start();
    }
}

在这里插入图片描述

案例

单例模式

更具体参考之前博客:java设计模式之单例模式

阻塞式队列

概念:

满足队列的结构和特性(链式或数组结构,先进先出),也满足线程安全:

在这里插入图片描述
好处:

在这里插入图片描述
生产者消费者模型

import java.util.Random;

public class 阻塞队列 {

    //循环数组:存取元素
    private int[] elements;
    //有效负荷
    private int size;
    //放元素的索引
    private int putIndex;
    //取元素的索引
    private int takeIndex;
    /**
     * @param capacity 容量
     */
    public 阻塞队列(int capacity){
        elements = new int[capacity];
    }

    /**
     * 放元素到阻塞队列:需要保证线程安全,如果队列满了,需要等待
     * @param element
     */
    public synchronized void put(int element) throws InterruptedException {
        //如果满了,就等
        while(elements.length == size){
            wait();
        }
        //如果不满,就放
        elements[putIndex] = element;
        //放的索引往后移动一位(取模是在最后一个位置就往首位移动)
        putIndex = (putIndex+1) % elements.length;
        //有效负荷+1
        size++;
        //通知wait等待的线程
        notifyAll();
    }

    /**
     * 取元素:需要保证线程安全,如果队列是空,需要等待
     * @return
     */
    public synchronized int take() throws InterruptedException {
        //如果是空,就等
        while(size == 0){
            wait();
        }
        //如果不空,就取
        int element = elements[takeIndex];
        //取的索引往后移动一位(取模是在最后一个位置就往首位移动)
        takeIndex = (takeIndex+1) % elements.length;
        //有效负荷-1
        size--;
        //通知wait等待的线程
        notifyAll();
        return element;
    }


    public static void main(String[] args) throws InterruptedException {
        阻塞队列 queue = new 阻塞队列(20);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟消费者
                while (true){
                    try {
                        int e = queue.take();
                        System.out.println("取到的数字:"+e);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        Thread.sleep(100);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟生产者
                while (true){
                    try {
                        queue.put(new Random().nextInt());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

    }
}

线程池

线程池:就是初始化(new线程池)的时候,就创建一定数量的线程(不停的从线程池内部的一个阻塞队列)取任务(消费者);我们就可以在其他线程中,提交任务(生产者)到线程池了。

jdk原生的线程池api

在这里插入图片描述

常见的锁策略

乐观锁 悲观锁

线程冲突:多个线程并发并行的对同一个共享变量操作(存在线程安全问题)

悲观锁:以悲观的心态看待线程冲突(总有刁民要害朕,总是觉得有其他线程会同时操作共享变量)。所以每次都加锁操作共享变量

乐观锁:以乐观的心态看待线程冲突(总是觉得没有线程会同时操作共享变量)。所以每次都不加锁(程序层面),就直接操作共享变量(依赖操作系统及cpu的一些功能,来操作(里边实际有加锁))

在这里插入图片描述

CAS

乐观锁(加锁的一种思想,程序看是无锁操作)的一种实现

在这里插入图片描述

CAS存在的问题:ABA问题

ABA问题:读和写操作,比较主存中变量的值时,值没有变,都是A,但实际已经被其他线程修改过了(A=>B=>A)

解决方式引入一个版本号的字段来解决

读写锁

优酷网站,某个用户上传了一个视频文件,这个用户可以修改(写操作),所有用户都可以播放(读操作)

在这里插入图片描述
满足:读读并发,读写,写写都是互斥

自旋锁

一般会搭配乐观锁一起来实现

在这里插入图片描述
通常结合CAS一起来保证线程安全的修改变量操作

CAS虽然是线程安全的无锁操作,但可能修改失败===>引入自旋的操作,满足不停的尝试修改

在这里插入图片描述

CAS+自旋实现线程安全无锁的++ vs synchronized加锁保证线程安全的++
cas+自旋: 一直处于可运行态(有些地方也直接说运行态)
所以如果尝试修改操作,能很快得到执行,效率就比较好(比较适用这种场景)。反之,不能很快得到执行,效率就差。

加锁操作: 申请锁失败会阻塞(线程状态会从运行态转变为阻塞态),锁释放以后,又会被唤醒再次竞争锁(阻塞态=>被唤醒)都有性能消耗

公平锁 非公平锁

非公平锁

synchronized申请对象锁,是竞争的方式

优点: 效率更高(不考虑执行顺序)
缺点: 可能出现线程饥饿(某些线程长期得不到执行)的现象

公平锁

以申请锁的时间先后顺序,来获取到锁——类似排队买票的方式

缺点: 效率稍微差一些

synchronized

对象头加锁的方式,实现线程安全:申请同一个对象锁时,多个线程间是同步互斥

对象中有一个对象头,其中就有一个状态的字段:无锁,偏向锁,轻量级锁,重量级锁。
synchronized申请锁成功,是后三种锁状态之一

在这里插入图片描述

jvm对synchronized的优化:锁升级

在这里插入图片描述

synchronized的其他优化

锁消除

在这里插入图片描述
锁粗化:共享变量,可能被其他线程持有,但不停的执行加锁释放锁操作,jvm就可以优化为第一次加锁,全部执行完再释放锁

在这里插入图片描述
在这里插入图片描述

Lock体系

在这里插入图片描述
在这里插入图片描述
lock:悲观锁

synchronized vs lock

1.Lock提供了非公平锁和公平锁,而synchronized只是非公平锁
2.线程竞争锁激烈的时候,使用Lock效率更高。
释放锁以后,synchronized是唤醒所有线程来竞争(涉及线程状态转换,就有性能消耗)。lock是唤醒一个(使用aqs来管理线程)

独占锁vs共享锁

独占锁

(1) synchronized加锁(2) Lock加锁 (3)写锁

共享锁

多个线程使用共享的锁,满足一定的条件,就可以并发并行的执行,不满足条件,就需要等待。

读锁属于共享锁

Semaphare:信号量

在这里插入图片描述
使用场景:
(1)有限资源的并发执行

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {

    private static Semaphore S = new Semaphore(5);

    public static void main(String[] args) {
        //模拟停车场同时停车,但不能超出有限的车位数量
        for (int i = 0; i < 20; i++) {
            final int j = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取一个车位
                        S.acquire();
                        //停车以后做其他事情
                        System.out.println(j);
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        //出停车场,归还车位
                        S.release();
                    }
                }
            }).start();
        }
    }
}

(2)多个线程并发执行,需要全部执行完,某个线程再执行后续的代码

import java.util.concurrent.Semaphore;

public class SemaphoreDemo2 {

    private static Semaphore S = new Semaphore(0);

    public static void main(String[] args) throws InterruptedException {
        //模拟停车场同时停车,但不能超出有限的车位数量
        for (int i = 0; i < 5; i++) {
            final int j = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(j);
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        S.release();
                    }
                }
            }).start();
        }
        S.acquire(5);
        System.out.println("main");
    }
}

System.out.println(“main”);会等到5个线程执行完后再执行。

CountDownLatch

在这里插入图片描述
Semaphore vs CountDownLatch
单纯的从api角度看,CountDownLatch的资源数只能减,semaphore是可以加也可以减。

死锁

双方都持有对方需要的锁,而发生双方无限期的等待锁释放

死锁产生的四个必要条件:

在这里插入图片描述
如何解决死锁问题:

破坏这个循环等待的条件 : 比如大家按照一定的顺序来申请锁

线程安全的集合

Map:重点

Hashtable

底层数据结构:数组+链表 相当于锁整个数组
特性: 所有方法都是synchronzied加锁保证线程安全
缺点: 效率不高

ConcurrentHashMap:并发的hashmap

1.8的实现
底层数据:数组+链表+红黑树

在这里插入图片描述

1.7的实现:基于数组+链表
实现方式:分段锁(Segment)

在这里插入图片描述

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

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

相关文章

Arch/Manjaro换源+安装常用的软件+安装显卡驱动

本文将教你&#xff1a;换源安装显卡驱动&#xff0c;安装常用软件例如腾讯会议&#xff0c;QQ&#xff0c;WPS 一起交流Linux知识&#xff0c;欢迎加入Skype群&#xff1a; Join conversationhttps://join.skype.com/q6wrF3d6Usni pacman换清华源 首先安装vim&#xff0c;用来…

(二十四)大白话RAID锂电池充放电导致的MySQL数据库性能抖动的优化

案例实战:RAID锂电池充放电导致的MySQL数据库性能抖动的优化 文章目录 1、磁盘故障怎么保障数据不丢失?2、线上MySQL数据库的性能定期抖动的原因1、磁盘故障怎么保障数据不丢失? 前面经过了几天的生产经验的一些铺垫,包括MySQL磁盘读写的机制,Linux存储系统的原理,RAID磁…

垃圾佬图拉丁装机

理论知识 缩线程 amd搞了个推土机架构 两个核心公用一个浮点运算单元&#xff0c;因为浮点运算只占百分之二十。 浮点运算应该交给更适合的gpu去做 好的对比 RDP 微软的RDP本身就定位是一个远程登录和维护windows系的工具&#xff0c;它为什么要支持管理别的系统&#xff…

【mybatis generator实战】 1.crud 2.计数 3.自定义复杂mapper代码组织

1.计数 2.CRUD 增 注意&#xff1a; insert&#xff1a;一个必须全部有值。 insertSelective是&#xff1a;部分有值就行&#xff0c;用的较多。 有疑问可以看源码&#xff0c;发现xxxSelective就是拼接了一些参数。 删 改 注意&#xff1a; 4个更新方法&#xff1a; …

QML学习笔记【06】:QML与C++交互

1 QML端直接调用C端变量及函数 1、 创建继承自QObject的C类&#xff0c;对象必须继承自QObject才能在QML被使用和访问 2、在类定义中使用Q_PROPERTY导出成员的READ、WRITE、NOTIFY接口&#xff0c;这样类中的成员变量就可以在QML调用和修改了&#xff0c;同时变量被修改后也会…

剑指 Offer 18. 删除链表的节点

一、题目描述 给定单向链表的头指针和一个要删除的节点的值&#xff0c;定义一个函数删除该节点。 返回删除后的链表的头节点。 示例 1: 输入: head [4,5,1,9], val 5 输出: [4,1,9] 解释: 给定你链表中值为 5 的第二个节点&#xff0c;那么在调用了你的函数之后&#xf…

2022年最后一篇推文 | C语言编程十诫

正文大家好&#xff0c;我是bug菌~2022年最后一篇推文原本选择一篇年终总结会比较合适&#xff0c;然而坐在窗台&#xff0c;望着窗外思索了良久&#xff0c;与往年总结有点不同&#xff0c;这个时间节点有着他的特殊性&#xff0c;不出意外&#xff0c;明年会有非常多的变化、…

OpenShift 4 - 用 HyperShift 实现以“托管集群”方式部署运行 OpenShift 集群

《OpenShift / RHEL / DevSecOps / Ansible 汇总目录》 说明&#xff1a;本文已经在 OpenShift 4.11 ACM 2.6 AWS 环境中验证 文章目录用 HyperShift 实现 OpenShift 托管集群什么是 HyperShift 托管集群以及架构HyperShift 托管集群的价值成本优势部署优势管理优势在 RHACM …

【Kaggle】Global Wheat Detection

代码链接 实验目的 小麦来自世界各地。密度的小麦植株经常重叠&#xff1b;风会使得照片模糊&#xff1b;外观会因成熟度&#xff0c;颜色&#xff0c;基因型和头部方向而异。使用图像处理和目标检测完成小麦头的位置的标定。完成训练并现场验证后上传指定的输出文件进行验证…

大数据NiFi(三):NiFi关键特性

文章目录 NiFi关键特性 一、​​​​​​​​​​​​​​流管理

人工智能轨道交通行业周刊-第28期(2022.12.26-2023.1.1)

本期关键词&#xff1a;NOCC、车站闸机、雾闪、2022年度盘点、智慧园区 1 整理涉及公众号名单 1.1 行业类 RT轨道交通中关村轨道交通产业服务平台人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通Rai…

安全—01day

DNS 域名解析过程: 1.浏览器首先查询浏览器的缓存&#xff0c;因为浏览器会按照一定的频率缓存 DNS 记录 2.若浏览器无缓存&#xff0c;那么查询操作系统的 HOST 文件&#xff0c;查询是否有 DNS 记录。 3.若还没有命中域名&#xff0c;就请求本地域名服务器该服务器一般都会缓…

Qt音视频开发07-合并音视频文件

一、前言 之前已经把音视频分开存储了对应的文件&#xff0c;因为这个需求特别少&#xff0c;当然确实有部分用户是需要把音视频分开存储&#xff0c;但是毕竟是很少数&#xff0c;绝大部分的用户都是音视频合并到一个MP4文件&#xff0c;所以如果要合并到一个文件&#xff0c…

二进制与十六进制的相互转换;按位操作:与() 和 或(|)

目录 一、二进制转换十六进制 二、十六进制转换二进制 三、按位操作&#xff1a;与&#xff08;&&#xff09; 和 或&#xff08;|&#xff09; 1、按位与&#xff08;&&#xff09;操作 2、按位或&#xff08;|&#xff09;操作 得出结论&#xff1a;清0用与&am…

【金猿案例展】海尔集团——追光AI-AOI赋能PCBA缺陷检测

‍数之联案例本项目案例由数之联投递并参与“数据猿年度金猿策划活动——《2022大数据产业年度创新服务企业》榜单/奖项”评选。‍数据智能产业创新服务媒体——聚焦数智 改变商业AOI&#xff08;Automated Optical Inspection缩写&#xff09;的中文全称是自动光学检测。通过…

className 还能这么用,你学会了吗

抛出问题 className大家都用过吧&#xff0c;用它在react项目中设置样式。它的用法很简单&#xff0c;除了可以设置一个样式外&#xff0c;react中也可以使用className引入多个类样式。 这次在写项目的时候&#xff0c;碰到一个非常小但是当时却一直解决不了的问题。后面在复…

python基础语法25-进程理论

一、简介 服务器为了能够同时为多个客户端服务,则需要能够同时处理多个网络连接的方法。python提供了3个主要的方法来实现这个目的,multiprocessing、threading和异步I/O。 今天主要说一下进程multiprocessing。 multiprocessing是一个和threading(多线程)类似的库,这个多…

【MVC和三层架构】联系、区别以及简单实践

MVC架构模式MVC架构模式概述引入MVCMVC和三层架构之间的联系和区别三层架构与MVC联系和区别银行转账小项目实践MVC架构模式概述 MVC架构模式是软件架构中的一个架构模式。 M&#xff08;Model&#xff1a;数据/业务&#xff09; V&#xff08;View&#xff1a;视图/展示&#x…

深入xJavaFxTool的插件开发模式

背景 时光华丽丽的来到了2023年&#xff0c;首先在这里祝大家新年快乐&#xff0c;希望大家在新的一年里都能心想事成&#xff0c;万事如意&#xff0c;最重要的是身体健康。过去的2022年里&#xff0c;大家一定都是收获满满。不论是技术的&#xff0c;家庭的&#xff0c;事业的…

【C++】map 与 set 的介绍与使用

目录 一、关联式容器 二、键值对 三、set 3.1 set 的介绍 3.2 set 的使用 3.3. set 的使用举例 四、map 4.1 map的介绍 3.2 map 的使用 4.3 map的使用举例 五、经典练习题 1.set的使用 2.map的使用 思路一(稳定排序)&#xff1a; 思路二(priority_queue)&#x…