Java并发

目录

线程

什么是线程

进程和线程的区别

线程的生命周期

什么是多线程

并发与并行

多线程的三种实现方式

继承Thread类

1.创建类继承Thread类

2.重写run()方法

3.创建对象启动线程

实现Runnable接口

1.自己定义一个类实现Runnable接口

2.重写run方法

3.创建自己类的对象

4.创建一个Thread类的对象,并开启线程

实现Callable接口与FutureTask类

1.实现Callable接口

2.重写call方法并return线程执行的结果

3.创建Callable对象

4.把Callable对象封装成FutureTask对象

5.把任务交给Thread类

6.获取执行的结果

Thread常用方法

Thread常用构造器 

为什么启动线程要调用start()方法而不是直接调run()方法

线程安全

线程同步

思想

常见方案

同步代码块

作用

原理

锁对象的使用规范

同步方法

作用

底层原理

Lock锁

线程通信

什么是线程通信

生产者与消费者模型

等待和唤醒方法

线程池

什么是线程池

不使用线程池的问题

线程池组成

线程池参数

线程池执行任务的流程

临时线程什么时候创建

什么时候会拒绝新任务 


线程

什么是线程

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。

 进程:进程是程序执行的基本实体。

拿任务管理器来举例, 我们可以看到其中的Edge、火绒等等这些应用程序我们就可以理解为他们是电脑里面的一个个进程。而在Edge中有九个就是在Edge这个进程里面对应的线程。

进程和线程的区别

根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

执行过程:每个独立的进程有程序运行的入口. 顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。

线程的生命周期

一个线程大致上有六种状态。

New新建:new Thread()出来的就是新建状态。

Runnable可运行:新建线程调用start方法。

Teminated被终止:线程执行完毕或者异常进入终止状态。

Blocked锁阻塞:当线程没有得到锁的时候便会进入锁阻塞状态,比如说a线程再访问一个需要锁的资源时,b线程也来访问,由于无法获得锁,便会进入锁阻塞状态,等到a线程释放锁之后b线程拿到锁便会回到Runnable可运行状态。

Waiting无限等待:当a线程对象使用wait()方法之后,会进入无限等待状态,只有其他线程比如b线程使用notify()唤醒了a线程如果a线程没有锁进入锁阻塞状态,如果有锁回到Runnable状态。

Timed Waiting计时等待:当线程从Runnable状态调用sleep(毫秒)或者wait(毫秒)之后会进入计时等待,时间结束之后。

回到Runnable:

  1. sleep时间到。  ps:sleep状态不会释放锁,而wait状态会释放锁。
  2. wait时间到并得到锁对象。
  3. wait时间没到,被其他线程notify并得到锁对象

进入Blocked:

wait时间到或者被notify未获得锁对象。

什么是多线程

在一个进程中同时执行多个线程的编程模型。线程是程序执行的最小单元,多线程编程允许程序在同一进程中的多个线程中并发执行,从而实现并行处理和提高系统的性能。

在多线程编程中,多个线程可以在同一时间片内交替执行,每个线程都有自己的执行路径和执行状态。不同于单线程程序只能按照顺序一步一步地执行,多线程程序可以同时执行多个任务,从而提高了并发性和响应性。

并发与并行

并发:在同一时刻,有多个指令在单个cpu上交替执行。

并行:在同一时刻,有多个指令在多个cpu上同时执行。

多线程的三种实现方式

继承Thread类

1.创建类继承Thread类
2.重写run()方法
package JUCDemo.demo1;
public class myThread extends Thread{
    @Override
    public void run() {
        for (int i = 0;i < 10;i ++){
            System.out.println(getName() + "Hello Thread");
        }
    }
}
3.创建对象启动线程
package JUCDemo.demo1;
public class demo1 {
    public static void main(String[] args) {
        /*
        1.定义一个类继承Thread类
        2.重写Run方法
        3.创建子类对象并启动线程
        */

        myThread t1 = new myThread();
        myThread t2 = new myThread();
        myThread t3 = new myThread();
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");

        t1.start();
        t2.start();
        t3.start();
    }
}

优点:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

缺点:线程类已经继承了Thread类,所以不能再继承其他父类。

实现Runnable接口

1.自己定义一个类实现Runnable接口
2.重写run方法
package JUCDemo.demo2;
public class myRunnable implements Runnable{

    @Override
    public void run() {
        //获取当前线程的对象
        Thread t = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            System.out.println(t.getName()+"线程执行");
        }
    }
}

3.创建自己类的对象
4.创建一个Thread类的对象,并开启线程
package JUCDemo.demo2;
public class demo2 {
    public static void main(String[] args) {
        myRunnable myRunnable = new myRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

优点:没有继承Thread类,所以可以继承其他的类,适合多线程访问同一资源的情况,将cpu和数据分开,形成清晰的模型,较好的体现了面向对象的思想

缺点:编程相对复杂,要想获得当前线程对象,需要使用Thread.currentThread()方法。

实现Callable接口与FutureTask类

1.实现Callable接口

其中我们要制定返回的参数值类型,这里使用String。

2.重写call方法并return线程执行的结果
package JUCDemo.demo3;
import java.util.concurrent.Callable;
//1.实现Callable接口
public class myCallable implements Callable<String> {
    int n = 0;
    public myCallable(int n) {
        this.n = n;
    }
    //2.重写Call方法
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum = sum + i;
        }
        return "线程执行1到" + n + "的和是:" + sum;
    }
}
3.创建Callable对象
4.把Callable对象封装成FutureTask对象

FutureTask

由于Callable对象是无法扔给Thread的,所以我们要通过封装成FutureTask对象的形式把我们的Callable对象交给Thread。

我们通过FutureTask的源码可以发现,他是实现了Runnable接口的。

5.把任务交给Thread类
6.获取执行的结果
package JUCDemo.demo3;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3.创建Callable对象
        Callable<String> callable = new myCallable(100);
        //4.把Callable对象封装成一个FutureTask对象(任务对象)
        //FutureTask的作用
        //    是一个任务对象,实现了Runnable接口
        //    可以在任务结束之后,调用get方法获取执行完毕的结果
        FutureTask<String> f1 = new FutureTask<>(callable);
        //5.把任务对象交给Thread对象
        new Thread(f1).start();
        //6.获取线程执行完毕的结果
        String rs = f1.get();
        System.out.println(rs);
    }
}

优点

线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。可以在线程执行完毕后去获得线程执行完毕的结果。

缺点

编码复杂。

Thread常用方法

Thread提供常用方法说明
pulic void run()线程的任务方法
public void start()启动线程
public String getName()获取当前线程的名称,默认是Thread-索引
public void setName(String name)为线程设置名字
public static Thread currentThread()获取当前执行的线程对象
public static void sleep(long time)让线程休眠
public final void join()调用当前这个方法让线程先执行完

Thread常用构造器 

Thread常用构造器说明
public Thread(String name)可以为当前线程指定名称
public Thread(Runnable target)封装Runnable对象为线程对象
public Thread (Runnable target,String name)封装Runnable对象为线程对象,指定名称

为什么启动线程要调用start()方法而不是直接调run()方法

  • new 一个 Thread,线程进入了新建状态; 调用start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,(调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。)这是真正的多线程工作。
  • 直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。

线程安全

Java线程安全问题是指在多线程环境下,多个线程同时访问共享资源时可能导致的数据不一致、数据损坏或程序异常等问题。这种问题可能发生在并发读写共享变量、共享数据结构或共享资源的情况下。

举个栗子,小明和小红共有一个银行账户,里面有100块钱。

当小明和小红都执行取钱操作的时候,系统会创建两个线程分别给小明和小红。

取钱分为两步,第一步判断余额,第二步账号减少余额并取出100。

当小明的线程判断完余额=100但是没有执行减少余额这个步骤时,小红线程进行了余额判断,那么系统便会分别给小明和小红100,这就是线程安全问题。

解决线程安全一般有三个方法,分别是:

线程同步

思想

让多个线程实现先后依次访问共享资源,这样就解决了线程安全问题。

常见方案

加锁:每次只允许一个线程加锁,加锁之后才能访问共享资源,访问完毕之后释放锁,然后其他线程才能再进行加锁操作并访问共享资源。

即,小明这个线程进入判断余额操作之后,会对判断余额上一把锁,上锁时小红线程就无法进行判断余额操作进入等待状态,只有当小明取钱完毕后小红才能进行余额判断。

同步代码块

作用

使用synchronized关键字把共享资源的核心代码块上锁,以保证此线程的安全。

原理

每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。 

锁对象的使用规范

  • 建议使用共享资源作为锁对象,对于实例方法建议this作为锁对象。
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象。

同步方法

作用

使用synchronized关键字把共享资源的核心方法上锁,以保证此线程的安全。

底层原理

  • 同步方法也是有隐藏式锁对象的,只是锁的范围是整个方法代码
  • 如果方法时实例方法:同步方法默认this作为锁对象。
  • 如果方法时静态方法:同步方法默认类名.class作为锁的对象。  

Lock锁

  • Lock锁时jdk5开始提供的一个新的锁定操作,通过它可以创建出锁对象来进行加锁和解锁,更灵活、更强大、更方便。
  • Lock是接口,不能直接实例化,需要使用它的实现类ReentrantLock来构建Lock锁对象。

线程通信

什么是线程通信

当多个线程共同操作共享的资源时,线程之间通过某种方式互相告知自己的状态,以相互协调,避免无效的资源争夺。

生产者与消费者模型

生产者线程负责生产数据。

消费者线程负责消费生产者线程生产的数据。

生产者生产完数据应该等待自己,通知消费者消费。消费者消费完数据也应该等待自己,再通知生产者生产。

等待和唤醒方法

方法名称说明
void wait()让当前线程等待并释放所占锁,知道另一个线程调用notify方法或notifyAll方法
void notify()唤醒正在等待的单个线程
void notifyAll()唤醒所有正在等待的线程

线程池

什么是线程池

线程池就是一个可以复用线程的技术

不使用线程池的问题

用户每发起一个请求,后台就要创建一个新线程来处理,下次新任务来了肯定又要创建新的线程来处理,而创建新的线程的开销是很大的,并且请求过多时,肯定会产生大量线程出来,这样会严重影响系统的性能。

线程池组成

线程池管理器(ThreadPoolExecutor):线程池管理器是线程池的核心组件,负责创建、管理和调度线程。
工作线程(WorkQueue):工作队列用于存储等待执行的任务。当有新的任务提交给线程池时,线程池将任务放入工作队列中,并由线程池中的线程按照一定的策略从队列中取出任务进行执行。
线程池线程(Worker Thread):线程池中的线程是执行任务的工作单元。任务队列中的线程对象必须是实现了Runnable接口或者Callable接口的才能进入任务对列。

线程池参数

  1. 参数一:corePoolSize:指定线程池的核心线程数量。
  2. 参数二:maxmumPollSize:指定线程池的最大线程数量。
  3. 参数三:keepAliveTime:指定临时线程存活时间。
  4. 参数四:unit:指定临时线程的存活时间单位。
  5. 参数五:workQueue:指定线程池的任务对列。
  6. 参数六:threadFactory:指定线程池的线程工厂。
  7. 参数七:handler:指定线程池的任务拒绝策略。

可以理解成一个公司,corePoolSize就是正式工数量,maxmumPollSize就是最大员工数量正式工+临时工,keepAliveTime是临时工多长时间后会被辞退,workQueue客人排队的地方。

线程池执行任务的流程

临时线程什么时候创建

核心线程在忙而且任务队列也满了。

什么时候会拒绝新任务 

核心线程和临时线程都在忙,任务队列也满了。

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

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

相关文章

java-11-openjdk-11.0.xxx/lib/tzdb.dat (No such file or directory)

项目用的是JAVA 11 build 的时候报错 ava-11-openjdk-11.0.xxx/lib/tzdb.dat (No such file or directory)这个问题困扰了很久&#xff0c;最终在redhat 上找到了root case: 该版本JDK 有bug 别挣扎了直接升级JDK

进程创建,程序加载运行,以及进程终止,什么是僵尸进程,什么是孤儿进程

进程控制 创建进程&#xff0c;撤销进程&#xff0c;实现进程转换&#xff08;必须一气呵成&#xff0c;使用原语&#xff09; 原语不被中断是因为有关中断指令 创建进程 撤销进程 进程创建fork fork&#xff08;&#xff09;函数会创建一个子进程&#xff0c;子进程会返…

HarmonyOS 应用开发案例

本帖下方集中了HarmonyOS Next应用开发时&#xff0c;会遇到的常见应用案例。后续会持续更新大量案例&#xff0c;帮助开发者快速学习。欢迎感兴趣的同学加入Q&#xff1a;454901491 72.手写绘制及保存图片案例&#xff08;0319更新&#xff09;&#xff08;点此查看源码实现&…

数字孪生与智慧城市:重塑城市生活的新模式

随着信息技术的迅猛发展&#xff0c;数字孪生作为一种新兴的技术理念&#xff0c;正在逐渐改变城市建设和管理的传统模式。智慧城市作为数字孪生技术应用的重要领域&#xff0c;正在以其独特的优势和潜力&#xff0c;重塑着城市生活的方方面面。本文将从数字孪生的概念、智慧城…

Nginx:部署及配置详解(linux)

Nginx&#xff1a;部署及配置详解&#xff08;linux&#xff09; 1、nginx简介2、安装编译工具及库文件3、安装 pcre4、nginx安装5、nginx配置文件nginx.conf组成6、nginx配置实例-反向代理7、nginx 配置实例-负载均衡 &#x1f496;The Begin&#x1f496;点点关注&#xff0c…

计算机组成原理 双端口存储器原理实验

一、实验目的 1、了解双端口静态随机存储器IDT7132的工作特性及使用方法 2、了解半导体存储器怎样存储和读出数据 3、了解双端口存储器怎样并行读写&#xff0c;产生冲突的情况如何 二、实验任务 (1)按图7所示&#xff0c;将有关控制信号和和二进制开关对应接好&#xff0c;…

Umi-OCR:开源、免费的离线OCR软件,一键解码万物语言,图像转文本轻松搞定!

Umi-OCR&#xff1a;瞬间捕获&#xff0c;字句跃然眼前&#xff01;精准识别图文信息&#xff0c;让数据提取无限拓展&#xff01; - 精选真开源&#xff0c;释放新价值。 概览 Umi-OCR是一款强大的开源光学字符识别&#xff08;OCR&#xff09;工具&#xff0c;致力于打破现实…

Arduino IDE工程代码多文件编程和中文设置

一、esp8266模块信息 二、中英文切换 点击文件( File )–选择首选项( Preference )—选择语言( Language )—选择中文–点击确定( OK ) 三、多文件编程 在Arduino编程中&#xff0c;将代码分割成多个文件是一种很好的做法&#xff0c;特别是项目变得越来越大和复杂时。这样…

SAP HCM 0008信息类型间接评估与直接评估

如果在间接评估模块输入就是间接评估&#xff08;tarif是读取下图中的数据 a代表不需要输入工资项&#xff0c;b表示需要找工资相&#xff09; 不输入就是直接评估需要客户自己输入数字 第2个情况 summe求和 &#xff08;比如在0008中输入9000与9001 那么自动求出9002工资项数…

分布式锁简单实现

分布式锁 Redis分布式锁最简单的实现 想要实现分布式锁&#xff0c;必须要求 Redis 有「互斥」的能力&#xff0c;我们可以使用 SETNX 命令&#xff0c;这个命令表示SET if Not Exists&#xff0c;即如果 key 不存在&#xff0c;才会设置它的值&#xff0c;否则什么也不做。 …

SQLiteC/C++接口详细介绍sqlite3_stmt类(九)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;六&#xff09; 下一篇&#xff1a; 无 33、sqlite3_column_table_name 函数 sqlite3_column_table_name 用于返回结果集中指定列所属的表的名称。如果查询中列使…

CTK插件框架学习-源码下载编译(01)

1、编译环境 window11、vs17、Qt5.14.0、cmake3.27.4 2、下载链接 cmake&#xff1a;Index of /files/v3.20 qt&#xff1a;Index of / vs22以前的版本需要登录下载&#xff1a;Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 vs22下载&#xff1a;下载 Visu…

Eclipse For ABAP:安装依赖报错

1.安装好Eclipse后需要添加依赖,这里的地址: https://tools.hana.ondemand.com/latest 全部勾选等待安装结束; 重启后报错:ABAP communication layer is not configured properly. This might be caused by missing Microsoft Visual C++ 2013 (x64) Runtime DLLs. Consu…

Java-SSM个人博客系统

Java-SSM个人博客系统 1.服务承诺&#xff1a; 包安装运行&#xff0c;如有需要欢迎联系&#xff08;VX:yuanchengruanjian&#xff09;。 2.项目所用框架: 前端:JSP、layui、bootstrap、jquery等。 后端:SSM,即Spring、SpringMvc、Mybatis等。 3.项目功能点: 1.前台功能: 1…

十、C#基数排序算法

简介 基数排序是一种非比较性排序算法&#xff0c;它通过将待排序的数据拆分成多个数字位进行排序。 实现原理 首先找出待排序数组中的最大值&#xff0c;并确定排序的位数。 从最低位&#xff08;个位&#xff09;开始&#xff0c;按照个位数的大小进行桶排序&#xff0c;将…

读算法的陷阱:超级平台、算法垄断与场景欺骗笔记17_执法工具

1. 执法工具箱 1.1. 在数据驱动的经济环境中&#xff0c;明智监管潜力无限 1.2. 多年前的司法体系与反垄断执法机构更善于发现市场漏洞&#xff0c;并设计出了直接有效的方式来化解问题 1.2.1. 大型互联网平台的权势凌驾于法律之上 1.2.1.1. 英国上议院 1.3. 反垄断执法机…

显卡基础知识及元器件原理分析

显卡应该算是是目前最为火热的研发方向了&#xff0c;其中的明星公司当属英伟达。 当地时间8月23日&#xff0c;英伟达发布截至7月30日的2024财年第二财季财报&#xff0c;营收和利润成倍增长&#xff0c;均超市场预期。 财报显示&#xff0c;第二财季英伟达营收为135.07 亿美…

【C++】1416. 求长方形的周长和面积

问题&#xff1a;1416. 求长方形的周长和面积 类型&#xff1a; 基本运算、整数运算 题目描述&#xff1a; 从键盘读入2个整数&#xff0c;分别代表一个长方形的长和宽&#xff0c;请计算长方形的周长和面积&#xff1b; 输入&#xff1a; 从键盘读入2个整数&#xff0c;用…

C#中文件操作打印当前目录下的所有文件名

首先&#xff0c;我们采取字符串保存我们当前目录&#xff0c;并且通过创建类来获取这个路径下的所有文件&#xff08;类下引用方法记得用小灯泡点亮&#xff09;&#xff0c;将文本数据导入数组 string path1 "C:/Users/Desktop"; DirectoryInfo root new Direct…

虚拟机扩展:虚拟机快照

虚拟机快照 在学习阶段我们无法避免的可能损坏Linux操作系统。如果损坏的话&#xff0c;重新安装一个Linux操作系统就会十分麻烦。 那我们就可以通过快照将当前虚拟机的状态保存下来&#xff0c;在以后系统损坏时通过快照恢复虚拟机到保存的状态。 制作并还原快照 在VMware …