Java基础之多线程JUC全面学习笔记

news2024/11/30 6:52:44

目录

  • 初识多线程
  • 多线程的实现方式
  • 常见的成员方法
  • 线程安全的问题
  • 死锁
  • 生产者和消费者
  • 线程池
    • 自定义线程池

初识多线程

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

简单理解:应用软件中互相独立,可以同时运行的功能

那什么是进程呢?
进程
进程是程序的基本执行实体,比如,打开任务管理器,可以看到其中有很多的进程

多线程作用:提高效率

多线程的两个概念

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

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


多线程的实现方式

①继承Thread类的方式进行实现

多线程的第一种启动方式:

1.自己定义一个类继承Thread
2.重写run方法
3.创建子类的对象,并启动线程

	public class MyThread extends Thread{
		@Override
		public void run() {
		//书写线程要执行代码
			for (int i = e; i < 100; i++) {
				system.out.println(getName( ) + "Helloworld" );
			}
		}
	}

		public static void main( String[] args) {
			MyThread t1 = new MyThread();
			MyThread t2 = new MyThread() ;
			t1.setName("线程1");
			t2.setName("线程2");
			t1.start();
			t2.start();
		}


②实现Runnable接口的方式进行实现

多线程的第二种启动方式:
1.自己定义一个类实现Runnable接口2.重写里面的run方法
3.创建自己的类的对象
4.创建一个Thread类的对象,并开启线程

    public static void main(String[] args) {
        //创建实现Runnable接口的类的对象
        RunnableInf run = new RunnableInf();
        //创建线程对象
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //给线程设置名字
        t1.setName("线程一");
        t2.setName("线程二");
        //开启线程
        t1.start();
        t2.start();

    }
public class RunnableInf implements Runnable{
    @Override
    public void run() {
        //书写线程执行代码
        for (int i = 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName()+"hello world");
        }
    }
}


③利用Callable接口和Future接口方式实现

多线程的第三种实现方式:

特点:可以获取到多线程运行的结果

步骤如下
1.创建一个类MyCallable实现callable接口
2.重写call (是有返回值的,表示多线程运行的结果)
3.创建MyCallable的对象(表示多线程要执行的任务)
4.创建FutureTask的对象(作用管理多线程运行的结果)
5.创建Thread类的对象,并启动(表示线程)

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建Mycallable的对象(表示多线程要执行的任务)
        MyCallable mc = new MyCallable();
        //创建FutureTask的对象(作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc ) ;
        //创建线程的对象
        Thread t1 = new Thread(ft);
        //启动线程
        t1.start();

        //获取线程运行结果
        Integer result = ft.get();
        System.out.println(result);
    }
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        //求100之间的和
        int sum = 0;
        for (int i = 0; i <= 100; i++ ){
            sum += i;
        }
        return sum;
    }
}


三种实现方式的对比

在这里插入图片描述


常见的成员方法

在这里插入图片描述

方法细节点

void setName( string name)
设置线程的名字(构造方法也可以设置名字)
细节:
1、如果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的)
2、如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置(记得继承Thread类的构造方法,快捷键Alt+Ins)


static Thread currentThread( )
获取当前线程的对象
细节:
当JVM虚拟机启动之后,会自动的启动多条线程其中有一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码,在以前,我们写的所有的代码,其实都是运行在main线程当中


static void sleep( long time)
让线程休眠指定的时间,单位为毫秒
细节:
1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
2、方法的参数:就表示睡眠的时间,单位毫秒;1秒= 1000毫秒
3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码


线程调度两种方式

  • 抢占式调度 最大特点是随机,根据优先级来抢占cpu资源,优先级越高,抢占概率越大
  • 非抢占式调度 特点:你一次,我一次,有规律
    在这里插入图片描述

优先级是0档到10档,默认优先级都是5
从Thread源码中可以看到

在这里插入图片描述


final void setDaemon( boolean on)
设置为守护线程细节:
当其他的非守护线程执行完毕之后,守护线程会陆续结束,不是立刻结束

通俗易懂:照下图案例
当女神线程结束了,那么备胎也没有存在的必要了

女神线程
在这里插入图片描述
备胎线程
在这里插入图片描述

main方法中
在这里插入图片描述

最后结果就是当线程1循环10次结束后,守护线程陆续结束(不会循环完,也不会立刻停止)


守护线程使用场景
比如QQ聊天,传输文件
在这里插入图片描述


出让线程解释

在这里插入图片描述


插入线程演示

    public static void main(String[] args) throws InterruptedException {
        //创建实现Runnable接口的类的对象
        RunnableInf run = new RunnableInf();
        //创建线程对象
        Thread t1 = new Thread(run);
        //Thread t2 = new Thread(run);
        //给线程设置名字
        t1.setName("线程一");    
        //开启线程
        t1.start();        
        //把t1线程插入到当前线程之前
        t1.join();  //这个代码运行在哪个线程上,就插入在哪个线程前

        for (int i = 0; i < 10; i++){
            System.out.println("main线程"+i);
        }
    }

线程的生命周期

在这里插入图片描述


线程安全的问题

由于线程抢占cpu资源是随机的,所以在执行业务操作时,可能同一个业务,多条线程并行执行,而造成数据错乱或丢失;比如买票的经典案例,有可能当一个线程进去执行买票逻辑时,另一个线程也进去了,结果导致卖出了两张同样编号的票。经典问题就是超卖,一票多卖,有票没卖。

解决方法自然就是给执行的业务代码加上锁,保证原子性


方法之一:同步代码块

把操作共享数据的代码锁起来

在这里插入图片描述

特点1:锁默认打卉,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开

同步代码块使用如下,卖100张票的案例

public class MyThread extends Thread{

    //表示这个类的所有对象共享ticket数据
    static int ticket = 0;

    //创建锁对象,一定要唯一
    static Object object = new Object();

    @Override
    public void run() {
        while (true){
            //同步代码块
            synchronized (object){
                if (ticket < 100){
                    ticket++;
                    System.out.println(getName()+"正在卖第" +ticket +"张票");
                }else {
                    break;
                }
            }
        }
    }
}

public class Treaddemo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("窗口1");
        t2.setName("窗口2");

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

方法之二:同步方法
在这里插入图片描述
就是把synchronized关键字加到方法上

特点1:同步方法是锁住方法里面所有的代码

特点2:锁对象不能自己指定
非静态: this
静态: 当前类的字节码文件对象


卖100张票的案例

public class MyRunnable implements Runnable{

    int ticket = 0;//由于MyRunnalbe方法只new了一个对象,可以不用把ticket设置为共享变量

    @Override
    public void run() {
        while (true){
            if (method()) break;
        }
    }

    private synchronized boolean method() {
        synchronized (MyRunnable.class){
            if ( ticket == 100 ){
                return true;
            }else {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                ticket++;
                System.out.println(Thread.currentThread().getName()+ "正在卖第"+ ticket +"张票");
            }
        }
        return false;
    }
}
public class Threaddemo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        Thread t3 = new Thread(mr);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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


方法之三:同步方法

为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,手动上锁、手动释放锁

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法

void lock():获得锁
void unlock():释放锁

Lock是接口不能直接实例化,通常采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法

ReentrantLock():创建一个ReentrantLock的实例

还是以买100张票为案例,使用锁的方式代码如下

public class LockThread extends Thread{
    static int ticket = 0;
    static Lock lock = new ReentrantLock();

    public LockThread() {
    }

    public LockThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (true){
            //这次换用锁的方式
            lock.lock();//上锁
            try {
                if (ticket < 100){
                    Thread.sleep(100);
                    ticket++;
                    System.out.println(getName()+"正在卖第" +ticket +"张票");
                }else {
                    break;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
    }
}

public class MainThread {
    public static void main(String[] args) {
        LockThread t1 = new LockThread("窗口一");
        LockThread t2 = new LockThread("窗口二");
        LockThread t3 = new LockThread("窗口三");

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

死锁

通俗点说就是锁嵌套了,线程1拿了A锁,就在线程1拿A锁时,线程2入了B锁,而线程1中间的操作还要再入B锁才能执行业务并结束;而线程2中间操作还要再入A锁才能执行业务并结束。最终二者都在等待对方释放锁,造成了死锁情况


生产者和消费者

等待唤醒机制
等待唤醒机制会让两个线程轮流执行,标准的你一次我一次

在这里插入图片描述

细节点:notify()是随机唤醒一个线程,不容易控制,一般使用的是notifyAll()方法

下面是经典的消费者和生产者案例代码,消费者是食客,生产者是厨师,中间控制是桌子,控制线程执行

中间控制者桌子

public class Desk {
    /**
     * 控制消费者和生产者的执行
     */

    //是否有面条 0:没有面条  1:有面条
    public static int foodFlag = 0;

    //总个数,也就是消费者需要的总个数
    public static int count = 10;

    //锁对象
    public static Object lock = new Object();
}

食客

public class Eater extends Thread{
    /**
     * 1.循环
     * 2.同步代码块
     * 3.判断共享数据是否到了末尾(到了末尾执行的逻辑)
     * 4.判断共享数据是否到了末尾(没到末尾执行的逻辑)
     */
    @Override
    public void run() {
        while (true){
            if (Desk.count == 0){
                break;
            }
            synchronized (Desk.lock){
                if (Desk.foodFlag == 0){//先看是否有产品,没有就等待
                    try {
                        Desk.lock.wait();//让当前线程跟锁绑定
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }else {
                    Desk.count--;
                    System.out.println("消费者正在消费产品,还需要消费数量:"+ Desk.count);
                    //消费完唤醒生产者继续做
                    Desk.lock.notifyAll();
                    //修改桌子状态
                    Desk.foodFlag = 0;

                }
            }
        }
    }
}

厨师

public class Cooker extends Thread{
    @Override
    public void run() {
        while (true){
            if (Desk.count == 0){
                break;
            }
            synchronized (Desk.lock){
                if (Desk.foodFlag == 1){
                    try {
                        Desk.lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }else {
                    //修改桌上食物状态
                    Desk.foodFlag = 1;
                    System.out.println("生产者生产了面条");
                    //唤醒消费者
                    Desk.lock.notifyAll();
                }
            }
        }
    }
}

main方法

public class TestDemo {
    public static void main(String[] args) {
        Eater e = new Eater();
        Cooker c = new Cooker();

        e.setName("消费者");
        c.setName("生产者");

        e.start();
        c.start();
    }
}


注意点
Desk. lock. wait(); //让当前线程跟锁进行绑定
Desk.Lock.notifyAll(); //唤醒跟这把锁绑定的所有线程
这里之所以用锁对象调用,是避免notifyAll()方法唤醒所有线程(包括方法外的比如系统线程等)用对象调用,可以指明唤醒的是哪个线程
(这里用Desk类中的lock对象调用,是应为lock对象是唯一的,只是在Desk类中new了一次)


线程池

在这里插入图片描述
用线程池作为容器存放线程
①创建一个池子,池子中是空的

②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可

③但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
在这里插入图片描述

在这里插入图片描述


自定义线程池

线程池工作流程,它会有核心线程数量,临时线程数量,队列长度,空闲时间这个几个因素影响决定

当任务数量超过核心线程数量时,就会让多余任务在队列排队
当任务数量超过核心线程数量,且队列排满的情况,才会使用临时线程来处理任务
当任务数量超过核心线程数量,最大排队数量以及临时线程数量的总和,全负载时,多余任务会根据线程池的拒绝策略来丢弃或处理

临时线程的空闲时间在超过线程池初始化规定的时间就会销毁
而核心线程只有在销毁线程池时才会销毁


创建线程池会发现有7个参数

在这里插入图片描述

最后任务拒绝策略有以下几种

在这里插入图片描述

创建自定义线程池代码如下

在这里插入图片描述


最后感谢您的阅览,希望这篇文章能为您解除疑惑

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

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

相关文章

为什么西门子、美的等企业这样进行架构升级,看看改造效果就知道了

在工业领域&#xff0c; 生产、测试、运行阶段都可能会产生大量带有时间戳的传感器数据&#xff0c;这都属于典型的时序数据。时序数据主要由各类型实时监测、检查与分析设备所采集或产生&#xff0c;涉及制造、电力、化工、工程作业等多个行业&#xff0c;具备写多读少、量非常…

从端到端打通模型端侧部署流程(NCNN)

文章目录背景介绍&#xff1a;为什么要做端侧推理&#xff1a;端侧深度学习部署流程&#xff1a;一条主要技术路线&#xff1a;ONNX&#xff1a;NCNN框架&#xff1a;NCNN的官方介绍&#xff1a;NCNN问题解决&#xff1a;NCNN使用样例快速在NCNN框架下验证自己的模型&#xff1…

数据分析思维(六)|循环/闭环思维

循环/闭环思维 1、概念 在很多的分析场景下&#xff0c;我们需要按照一套流程反复分析&#xff0c;而不是进行一次性的分析&#xff0c;也就是说这套流程的结果会成为该流程的新一次输入&#xff0c;从而形成一个闭环&#xff0c;此时的分析思维我们称之为循环/闭环思维。 常…

计算机断层扫描结肠镜和全自动骨密度仪在一次检查中的可行性

计算机断层扫描结肠镜和全自动骨密度仪在一次检查中的可行性 Feasibility of Simultaneous Computed Tomographic Colonography and Fully Automated Bone Mineral Densitometry in a Single Examination 简单总结&#xff1a; 数据&#xff1a;患者的结肠镜检查和腹部CT检查…

2022黑马Redis跟学笔记.实战篇(三)

2022黑马Redis跟学笔记.实战篇 三4.2.商家查询的缓存功能4.3.1.认识缓存4.3.1.1.什么是缓存4.3.1.2.缓存的作用1.为什么要使用缓存2.如何使用缓存3. 添加商户缓存4. 缓存模型和思路4.3.1.3.缓存的成本4.3.2.添加redis缓存4.3.3.缓存更新策略4.3.3.1.三种策略(1).内存淘汰:Redis…

NoSQL和Redis

NoSQL一、NoSqlNoSQL Not Only SQL(不仅仅是SQL)非关系型数据库二、为什么需要NoSQL1、web1.0在90年代&#xff0c;一个网站的访问量一般都不大&#xff0c;用单个数据库完全可以轻松应付。在那个时候&#xff0c;更多的都是静态网页&#xff0c;动态交互类型的网站不多。单机…

CS224W课程学习笔记(一):课程介绍与图深度学习概念

引言 我们从怎么利用图形或网络表示数据这一动机开始。网络成为了用于描述复杂系统中交互实体的通用语言。从图片上讲&#xff0c;与其认为我们的数据集由一组孤立的数据点组成&#xff0c;不如考虑这些点之间的相互作用和关系。 在不同种类的网络之间进行哲学上的区分是有启…

系统功能设计:教育缴费平台产品需求文档

教育缴费系统后台能够支撑前端业务&#xff0c;查询所需字段&#xff0c;为支撑前端业务提供服务&#xff0c;支持学校分校管理、班级分班管理、账单撤回及强制结束等功能。为了将教育缴费的需求清晰准确地描述清楚&#xff0c;本文作者编写了这个产品需求文档&#xff0c;一起…

Jmeter自带函数不够用?不如自己动手开发一个

在Jmeter的函数助手里&#xff0c;有很多内置的函数&#xff0c;比如Random、UUID、time等等。使用这些函数可以快速帮我们生成某些数据&#xff0c;进行一些逻辑处理。用起来非常的方便。 但是在实际接口测试过程中&#xff0c;有很多的需求&#xff0c;Jmeter内置的函数可能…

对抗生成网络GAN系列——Spectral Normalization原理详解及源码解析

&#x1f34a;作者简介&#xff1a;秃头小苏&#xff0c;致力于用最通俗的语言描述问题 &#x1f34a;专栏推荐&#xff1a;深度学习网络原理与实战 &#x1f34a;近期目标&#xff1a;写好专栏的每一篇文章 &#x1f34a;支持小苏&#xff1a;点赞&#x1f44d;&#x1f3fc;、…

JavaEE-HTTP协议(二)

目录HTTP请求的方法GET方法POST 方法其他方法“报头”User-AgentRefererCookieHTTP响应200 OK404 Not Found403 Forbidden405 Method Not Allowed500 Internal Server Error504 Gateway Timeout302 Move temporarily301 Moved PermanentlyHTTP请求的方法 GET方法 GET 是最常用…

Jmeter之直连数据库框架搭建简介

案例简介 通过直连数据库让程序代替接口访问数据库&#xff0c;如果二者预期结果不一致&#xff0c;就找到了程序的缺陷。 下面通过一个案例分析讲解如何实现&#xff1a;获取某个字段值&#xff0c;放在百度上搜索。 实现方式 1、Jmeter本身不具备直连数据库的功能&#xf…

机器学习笔记之生成模型综述(四)概率图模型 vs 神经网络

机器学习笔记之生成模型综述——概率图模型vs神经网络引言回顾&#xff1a;概率图模型与前馈神经网络贝叶斯网络 VS\text{VS}VS 神经网络表示层面观察两者区别推断、学习层面观察两者区别引言 本节将介绍概率图模型与神经网络之间的关联关系和各自特点。 回顾&#xff1a;概率…

Javaweb安全——Dubbo 反序列化(一)

Dubbo 反序列化&#xff08;一&#xff09; Dubbo 基础 Apache Dubbo 是一款 RPC 服务开发框架。提供三个核心功能&#xff1a;面向接口的远程方法调用、智能容错和负载均衡&#xff0c;以及服务自动注册和发现。 节点角色 节点角色说明Provider暴露服务的服务提供者Consume…

leaflet 加载KML数据显示图形(方法3)

第061个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载kml文件,并解析后在地图上显示图形,这里是第三种方法,前两种方法请参考目录查询。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共81行…

大数据培训课程分享:Python数据分析与挖掘实战课程介绍

《Python数据分析与挖掘实战》课程内容以Python数据分析与挖掘的常用技术与真实案例相结合的方式&#xff0c;深入浅出地介绍Python数据分析与挖掘的重要内容&#xff0c;共分为基础篇&#xff08;第1~5章&#xff09;和实战篇&#xff08;第6~11章&#xff09;。 基础篇内容包…

Git 安装和使用(非常详细教程)

Git 安装和使用Tips 目录&#xff1a;导读 1. git的安装 1)首先去下载 2)傻瓜式下一步再下一步地去安装 2. git的常见命令 提交代码 下载代码 分支提交代码 3. git的常见问题 1) 提示出错信息&#xff1a;fatal: remote origin already exists. 2) 发现日志等文件没…

通过异常处理错误

写在前面Java的基本理念是"结构不佳的代码不能运行"。发现错误的理想时机是在编译阶段, 也就是在你试图运行程序之前。然而, 编译期间并不能找出所有的错误, 余下的问题必须在运行期间解决。这就需要错误源能通过某种方式, 把适当的信息传递给某个接收者——该接收者…

情人节特刊 | “恋爱容易,相守难!” 犀思老兵谈破局之道!

付出甘之如饴&#xff0c;所得归于欢喜。 主动付出真心&#xff0c;问心无愧&#xff0c;未来无悔。老吴是我们公司十多年经验的售后服务主管&#xff0c;平时聊的不多&#xff0c;中午一起吃饭&#xff0c;偶然看到新闻说春节后多地都有排队办理离婚的现象。我不禁感叹一句&am…

三种查找Windows10环境变量的方法

文章目录一.在设置中查看二. 在我的电脑中查看三. 在资源管理器里查看一.在设置中查看 在系统中搜索设置 打开设置&#xff0c;在设置功能里&#xff0c;点击第一项 系统 在系统功能里&#xff0c;左侧菜单找到关于 在关于的相关设置里可以看到高级系统设置 点击高级系…