java学习--多线程

news2024/10/2 22:22:45

多线程

在这里插入图片描述

了解多线程

​ 多线程是指从软件或者硬件上实现多个线程并发执行的技术。

​ 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。

并发和并行

  • 并行:在同一时刻,有多个指令在CPU上同时执行
  • 并发:在同一时刻,有多个指令在CPU上交替执行

进程和线程

进程:正在运行的软件

  • 独立性:进程是一个独立运行的基本单位,同时也是系统分配调度资源的独立单位。
  • 动态性:进程的实质就是程序的一次执行过程,动态产生,动态消亡。
  • 并发性:任何进程都可以和其他进程并发执行

线程:是进程中单个顺序控制流,是一条执行路径

  • 单线程:一个进程如果只有一条执行路径,则称为单线程程序
  • 多线程:一个进程如果有多条执行路径,则成为多线程程序

多进程的实现方案

  • 继承Thread类的方式进行实现
    • 定义一个类继承Thread类
    • 在定义的类中重写run()方法
    • 创建自定义类的对象
    • 启动线程
  • 实现Runable接口的方式实现
    • 自定义一个类实现Runable接口
    • 在自定义类中重写run()方法
    • 创建自定义对象
    • 创建Tread类对象,把自定义对象作为构造方法的参数
    • 启动线程
  • 利用Callable和Future接口方式实现
    • 定义一个类实现Callable接口
    • 在该类中重写call方法
    • 创建自定义类的对象
    • 创建Future的实现类FutureTask对象,把自定义类对象作为构造方法的参数
    • 创建Tread类的对象,把FutureTask对象最为构造方法的参数
    • 启动线程

三种方式的对比

优点缺点
实现Runnable,Callable接口扩展性强,实现该接口的同时还可以继承其他类编程相对复杂,不能直接使用Thread类中的方法
继承Thread类编程比较简单,可以直接使用Thread类中的方法可扩展性差,不能继承其他类

使用getName()获取当前正在执行现成的名称

使用setName()给当前线程设置名称,也可使用子类的带参构造方法设置名称

使用Thread的Sleep()方法 使得线程睡眠特定的时间

实现Runnable结构创建多线程程序的好处

  1. ​ 避免了单继承的局限性
    • 一个类只能继承一个类,自定义类继承了Thread类就不能继承其他类
    • 实现了Runnable接口,还可以继承其他类,实现其它接口
  2. 增强了程序的扩展性,降低了程序的耦合性
    • 实现了Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
    • 实现类中,重写了run方法,用来设置线程任务
    • 创建Thread类对象,调用start方法,用来开启新的线程

线程安全问题

解决措施:

使用同步机制解决
方法一:同步代码块
syncheonized(对象){
	代码块
}
方法二:同步方法

使用步骤:

  • 把访问了共享数据的代码提取出来,放到一个方法中
  • 在方法上添加synchronized修饰符
  • 格式
修饰符 synchronized 返回值类型 方法名(参数列表){
	可能会出现安全问题的代码(访问了共享数据的代码)
}

线程池

线程池可以看成是一个池子,这个池子中存储很多个线程

系统创建一个线程的成本是很高的,因为他涉及到与操作系统的交互,当程序需要创建大量生存期很短暂的线程是,频繁的创建何晓辉线程对系统的资源消耗可能大于业务处理对线程的消耗。为了提高性能,我们可以采用线程池。

线程池在启动时,会创建大量空闲线程,当我们向线程池提价搜任务是,线程池就会启动一个线程来执行该任务。等待任务执行完毕,线程并不会死亡,而是咋次返回到线程池中称为空闲状态,等待哦下一次任务的执行。

线程池的设计思路

  1. 准备一个任务容器
  2. 一次性启动多个消费者线程
  3. 刚开始任务容器是空的,所有线程都在等待
  4. 知道一个外部线程向这个任务容器扔了一个“任务”,就会有一个消费者线程被唤醒
  5. 这个消费者线程取出任务,并执行任务,执行完毕后,继续等待下一次任务的到来
线程池-Executors默认线程池

概述:在开发中我们使用JDK中自带的线程池

我们可以使用Excutors中所提供的的静态方法来创建线程池

static ExcutorsService newCachedThreadPool()创建一个默认线程池

static newFixedThreadPool(int nThreads)创建一个指定最多线程数量的线程池

代码实现

ackage practise2.Exam2;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//static ExecutorsService new CachedThreadPool() 创建一个默认的线程池
//static newFixedThreadsPool(int nThreads) 创建一个指定最多线程数量的线程池
public class Exam1 {
    public static void main(String[] args) {
        //创建一个默认线程池对象,池子是空的,默认最多可以容纳int类型的最大值
        ExecutorService executorService= Executors.newCachedThreadPool();
        //Executors ---可以帮助我们创建线程池对象
        //ExecutorService ---可以帮助我们控制线程池

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"在执行了");
        });
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"在执行了");
        });
        executorService.shutdown();
    }
}

线程池-Executors创建指定上限的线程池

使用Executors中所提供的静态方法来创建线程池

static ExecutorsService newFixcedThreadPool(int nThread):创建一个指定最多线程数量的线程池

代码实现:

package practise2.Exam2;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

//static ExecutorService newFixedThreadPool(int nThread):创建一个指定最多线程数量的线程池
public class Exam2 {
    public static void main(String[] args) {
        //参数不是初始值而是最大值
        ExecutorService executorService= Executors.newFixedThreadPool(10);

        ThreadPoolExecutor pool= (ThreadPoolExecutor) executorService;
        System.out.println(pool.getPoolSize()); //0

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"在执行了");
        });
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"在执行了");
        });

        System.out.println(pool.getPoolSize());//2
        //executorService.shutdown();
    }
}
线程池-ThreadPoolExecutor

创建线程池对象:

ThreadPoolExecutors threadPoolExecutor =new ThreadPoolExecutor(核心线程数连发,最大线程数量,空闲线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

代码实现

package practise2.Exam2;

import pracise1.exam5.MyRunnable;

import java.util.concurrent.*;

public class Exam3 {
    public static void main(String[] args) {
        //参数一:核心线程数量
        //参数二:最大线程数
        //参数三:空闲线程最大存活时间
        //参数四:时间单位
        //参数五:任务队列
        //参数六:创建线程工厂
        //参数七:人物的拒绝策略
        ThreadPoolExecutor pool =new ThreadPoolExecutor(2,5,2, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool.shutdown();

    }
}
线程池-参数详解

创建线程池对象

ThreadPoolExecutor threadPoolExecutor =new ThreadPoolExecutor(

核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略

)

参数含义限制
参数一核心线程数量不能小于0
参数二最大线程数不能小于等于0,最大线程数大于等于核心线程数
参数三空闲线程最大存活空间不能小于0
参数四时间单位时间单位
参数五任务队列不能为null
参数六创建线程工厂不能为null
参数七人物的拒绝策略不能为null
public ThreadPoolExecutor(int corePoolSize,
                        int maximumPoolSize,
                        long keepAliveTime,
                        TimeUnit unit,
                        BlockingQueue<Runnable> workQueue,
                        ThreadFactory threadFactory,
                        RejectedExecutionHandler handler)
corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 时间单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null

线程池-非默认任务拒绝策略

RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,他下面存在4个子类。

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor。CallerRunsPolicy: 调佣任务的run()方法绕过线程池直接执行

注:明确线程池对多可执行的任务数=队列容量+最大线程数

package practise2.Exam2;

import java.util.concurrent.*;

public class Exam {
    public static void main(String[] args) {
//        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,3,20,
//                TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),
//                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        //提交5个任务,而该线程池最多可以处理4个任务,当我们使用ABortPOlicy这个任务处理策略是后,就会抛出异常
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,3,20,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
        //提交了5个任务
        for (int i = 0; i < 5; i++) {
            //定义一个变量,来制定当前执行的任务,这个变量需要被final修饰
            final int y=i;
            threadPoolExecutor.submit(()->{
                //System.out.println(Thread.currentThread().getName()+"------>>执行了任务");
                System.out.println(Thread.currentThread().getName()+"---->>执行了任务"+y);
            });
        }
    }
}

package practise2.Exam2;

import java.util.concurrent.*;

public class Exam5 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(
                1,3,20, TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),
                Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy()
        );
        //提交五个任务
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.submit(()->{
                System.out.println(Thread.currentThread().getName()+"---->>执行了任务");
            });
        }
    }
}

通过控制台的输出,我们可以看次策略没有通过线程池中得到线程执行任务,而是直接调用任务的run()方法绕过线程池直接执行。

原子性

volatile-问题

代码分析:


volatile解决
以上案例出现的问题:

当A线程修改了共享数据时,B线程没有及时获取道最新的值,如果还在使用原先的值,就会出现问题。

  1. 堆内存是唯一的,每一个线程都有自己的线程栈
  2. 每一个线程在使用堆内存里面的变量时,都会先拷贝一份到变量的副本中
  3. 在线程中,每一次使用是从变量的副本中获取的

volatile关键字:强制线程在每一次使用时,都会看一下公共区域最新的值。

public class Money {
    public static volatile int money=100000;
}
public class MyThread1 extends Thread{
    @Override
    public void run() {
        while (Money.money==100000){}
        System.out.println("结婚基金已经不是十万了");
    }
}
public class MyThread2 extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        Money.money=90000;
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 myThread1=new MyThread1();
        myThread1.setName("小垃圾");
        myThread1.start();

        MyThread2 myThread2=new MyThread2();
        myThread2.setName("小趴菜");
        myThread2.start();
    }

}

synchronized解决
  1. 线程获得锁
  2. 清空变量副本
  3. 拷贝共享最新的值到变量副本中
  4. 执行代码
  5. 将修改后变量副本中的值赋值给共享数据
  6. 释放锁

代码实现


原子性

概述:在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可分割的整体。

Volatile关键字不能保证原子性

解决方案:我们可以给count++操作添加锁,那么count++操作就是临界区中的代码,临界区中的代码一次只能被一个线程去执行,所以count++就变成了的原子操作。


原子性—AtomicInteger

概述:java从JDK1.5开始提供了java.uyil.concurrent.atomic包(简称Atomic包),这个包中的原子提供了一种用法简单,性能高效,线程安全地根新一个变量的方式,因为变量的类型有很多个,所以在Atomic包中一共提供了13个类,属于4种类型的原子更新方式,分别是:

原子更新基本类型,原子更新数组,原子更新引用和原子更行属性(字段)

使用原子的方式甘心基本类型,使用原子的方式更新基本类型Atomic包提供了一下3个类:

AtomicBoolean:原子更新布尔类型

AtomicInteger:原子更新整型

AtomicLong:原子更新长整型

以上三个类提供的方法几乎一模一样,以AtomicInteger为例讲解

public AtomicInteger(); 	//初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue):	//初始化一个指定值的原子型Integer

int get();								//获取值
int getAndIncrement();					//以原子的方式将当前值加1,注意,这里返回的是自增前的值
int incrementAndGet();					//以原子的方式将当前值加1,注意,这里返回的是自增前的值
int addAndGet(int data):				//以原子的方式将输入的数值与示例中的值(AtomicInteger里的value)相加,并返回结果。
int getandSet(int value):				//以原子方式设置为newValur的值,并返回旧值

代码实现:


AtomicInteger-内存解析

AtomicInteger原理:

自旋锁+CAS算法

CAS算法:

有三个操作数(内存值V,旧的预期值A,要修改的值B)

当旧的预期值A==内存值 此时修改成功,将V改为B

当旧的预期值A!=内存值,此时修改失败,不做任何操作

并重新获取现在的最新值(这个重新获取的动作就是自旋)

AtomicInteger-源码解析
代码实现:

源码解析

乐观锁和悲观锁

synchronized和CAS的区别

相同点:

​ 在多线程的情况下,都可以保证共享数据的安全性

不同点:

synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每次操作共享数据之前,都会上锁。(悲观锁)

cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁,只不过在修改共享数据的时候,会检查一下。

如果别人修改过,则获取最新数据。

如果别人没有修改过,那么我们直接修改共享数据的值(乐观锁)

并发工具类

并发 工具类-Hashtable
Hashtable出现的原因:

​ 在集合类中HashMap是比较常用的集合类对象,但是HashMap是现成不安全的(多线程环境下可能会存在问题)。为了保证数据得到安全性我们可以使用Hashtable,但是Hashtable的效率低下。

代码实现:


并发工具类-ConcurrentHashMap基本使用
ConcurrentHashMap出现的原因

​ 在集合类中HashMap是比较常用的集合对象,但是hashMap是线程不安全的多线程环境下可能会存在的问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下,基于以上两个原因我们可以使用JDK1.5以后提供的ConcurrentHashMap。

体系结构

ConcurrentHashMap

Map接口

  1. HashMap
  2. Hashtable
  3. TreeMap
  4. ConcurrentMap
总结:
  1. HashMap是线程不安全的,多线程环境下有数据安全问题
  2. hashtable 是线程安全的,但是会将整张表锁起来,效率低下‘
  3. ConcurrentHashMap也是线程安全的,效率高,在JDK7和JDK8中,底层原理不同

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

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

相关文章

20230217使AIO-3399J开发板上跑通Android11系统

20230217使AIO-3399J开发板上跑通Android11系统 2023/2/17 15:45 1、解压缩SDK&#xff1a;rk3399-android-11-r20211216.tar.xzrootrootrootroot-X99-Turbo:~$ tar xvf rk3399-android-11-r20211216.tar.xz 2、编译U-boot&#xff1a; rootrootrootroot-X99-Turbo:~/rk3399-a…

没有接口文档的怎样进行接口测试

前言&#xff1a; 在进行接口测试之前&#xff0c;一般开发会提供接口文档&#xff0c;给出一些接口参数和必要熟悉&#xff0c;便于我们编写接口脚本。但如果没有提供接口开发文档的请求下&#xff0c;我们该如何编写接口测试脚本呢&#xff1f;在编写测试脚本前要做哪些必要…

一台电脑安装26个操作系统(windows,macos,linux)

首先看看安装了哪些操作系统1-4: windows系统 四个5.Ubuntu6.deepin7.UOS家庭版8.fydeOS9.macOS10.银河麒麟11.红旗OS12.openSUSE Leap13.openAnolis14.openEuler(未安装桌面UI)15.中标麒麟&#xff08;NeoKylin&#xff09;16.centos17.debian Edu18.fedora19.oraclelinux20.R…

CCNP350-401学习笔记(1-50题)

1、Which function does a fabric edge node perform in an SD-Access deployment?A. Connects endpoints to the fabric and forwards their traffic. B. Encapsulates end-user data traffic into LISP. C. Connects the SD-Access fabric to another fabric or external La…

YOLOv5:GitHub两万八Star项目

来源&#xff1a;投稿 作者&#xff1a;王同学 编辑&#xff1a;学姐 Yolov5详解 官方源码仓库&#xff1a;https://github.com/ultralytics/yolov5 相关论文&#xff1a;未发表&#xff08;改进点都被你们抢先发了&#xff09; 0 前言 截止到2022年7月&#xff0c;Yolov5项…

docker目录迁移流程

概述 在安装测试最新版本的HOMER7的过程中&#xff0c;docker作为基础工具碰到一些问题&#xff0c;针对问题进行总结。 docker的默认工作目录在/var目录&#xff0c;而在我们的环境中&#xff0c;/var目录空间预留不足&#xff0c;随着docker的运行&#xff0c;/var目录空间…

WiFi网络带宽、流量监控管理

当您的组织拥有越来越多的有线和无线设备时&#xff0c;有必要在预算、性能和安全性之间取得准确的平衡。尽管无线设备可以为用户提供灵活性&#xff0c;但鉴于其动态性质&#xff0c;发现和管理这些设备可能极具挑战性。 为用户提供功能性无线网络性能&#xff0c;同时确保没…

Spire.Office 8.2.2 for NET 开年之喜

Spire.Office for .NET对文档的操作包括打开&#xff0c;创建&#xff0c;修改&#xff0c;转换&#xff0c;打印&#xff0c;浏览 Word、Excel、PowerPoint 和 PDF 文档&#xff0c;以及将数据从数据源导出为常用的文档格式&#xff0c;如&#xff1a;Word&#xff0c;Excel&a…

每天10个前端小知识 【Day 16】

&#x1f469; 个人主页&#xff1a;不爱吃糖的程序媛 &#x1f64b;‍♂️ 作者简介&#xff1a;前端领域新星创作者、CSDN内容合伙人&#xff0c;专注于前端各领域技术&#xff0c;成长的路上共同学习共同进步&#xff0c;一起加油呀&#xff01; ✨系列专栏&#xff1a;前端…

【老王读Spring Transaction-6】spring-tx与DataSource连接池整合的原理

06spring-tx与DataSource连接池的原理前言Spring 版本正文事物连接: Spring-managed transactional Connectionsspring-tx 与 DataSource 的整合SpringBoot 默认使用的 DataSource 连接池是 Hikari事物连接的关闭/释放: 并不是真正的关闭小结前言 javax.sql.DataSource 是 jav…

2月,还是不要跳槽

新年已经过去&#xff0c;马上就到金三银四跳槽季了&#xff0c;一些不满现状&#xff0c;被外界的“高薪”“好福利”吸引的人&#xff0c;一般就在这时候毅然决然地跳槽了。 在此展示一套学习笔记 / 面试手册&#xff0c;年后跳槽的朋友可以好好刷一刷&#xff0c;还是挺有必…

十四、vue3项目如何使用three.js

近期在开发过程中&#xff0c;因为项目已经接近尾声&#xff0c;就需要对项目中的数据进行整合&#xff0c;而数据看板不失为一个比较直观的展现形式。在数据看板中3D的展现形式是比较流行的展现形式&#xff0c;那么如何在项目引入一个大的场景&#xff0c;并且能够和后台发生…

【NGINX入门指北】Nginx Web 架构实验

Nginx Web 架构实验 文章目录Nginx Web 架构实验一、动态网站结构二、LNMP 动态网站环境部署三、fastcgi & php-fpm&#xff1a;四、php-fpm初始化配置五、Nginx Location、六、Nginx Rewrite七、CA&HTTPS八、Nginx 的平滑升级一、动态网站结构 资源 资源文件识别——…

数据结构---时间复杂度

专栏&#xff1a;数据结构 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;开学数据结构&#xff0c;接下来会慢慢坑新数据结构的内容&#xff01;&#xff01;&#xff01;&#xff01; 时间复杂度前言1.算法效率1.1如何衡量一个算法的好坏1.2算法的复杂度2.时间复杂度2.1大…

Leetcode力扣秋招刷题路-0073

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 73. 矩阵置零 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;mat…

Seata1.5.2安装配置(nacos)+部署

一、seata服务端下载&#xff0c;下载方式介绍两种入口&#xff0c;如下&#xff1a; 1. seata官网 (http://seata.io/zh-cn/blog/download.html) 下载中心 找到对应版本&#xff0c;下载 binary 即可。 下载包名为&#xff1a;seata-server-1.5.2.zip 2. github上下载 Rel…

HIVE 基础(一)

目录 启动hive 方式一 方式二 修改hdfs上给定文件执行的读写权限 创建数据库 查看数据库 查看数据库详细信息 查看当前数据库 创建表 查看建表语句 查看表信息 删除表 添加数据 查看表数据 删除数据库 强制删除数据库 启动hive 方式一 [roothadoop1 ~]# hive 方…

遥感数字图像处理

遥感数字图像处理 来源&#xff1a;慕课北京师范大学朱文泉老师的课程 遥感应用&#xff1a;遥感制图、信息提取 短期内了解知识结构–>有选择的剖析经典算法原理–>系统化知识结构、并尝试实践应用 跳出算法&#xff08;尤其是数学公式&#xff09; 关注原理及解决问…

PowerShell中的对象是神马?

在PowerShell中,无处不在体现出一个概念,这个概念是什么呢?就是对象,对象是面向对象的语言中非常重要的概念,PowerShell的底层是.net,也是面向对象的语言,因此它也继承了面向对象的语言的语法特性。但是很多人在使用PowerShell 语言的时候会觉得有些疑惑,到底什么是Pow…

PCB学习笔记——使用嘉立创在线绘制原理图与PCB

嘉立创软件地址&#xff1a;https://lceda.cn/ 新建工程-新建原理图&#xff0c;在元件库中可以搜索元器件&#xff0c;可以直接放置在原理图上。 原理图绘制完成后&#xff0c;保存文件&#xff0c;设计-原理图转PCB&#xff0c;可以直接生成对应的PCB&#xff0c;设置边框&…