JAVA多线程(MultiThread)的各种用法

news2024/11/30 0:37:03

多线程简单应用

单线程的问题在于,一个线程每次只能处理一个任务,如果这个任务比较耗时,那在这个任务未完成之前,其它操作就会无法响应。

如下示例中,点击了“进度1”后,程序界面就没反应了,强行拖动容器后变成了“无响应”。

使用线程之前

 

 其原因是这段循环代码处于独占状态,这里并没有给其它代码执行的机会,包括接收界面更新的后台消息,导致应用程序处于一个假死的状态。只有等这个循环退出后,才可以进行其它的操作。

        for (double i = 0; i < 100.0; i++) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            progressBar0.setProgress(i);

        }

 接下来,我们把这个循环代码放到一个单独的线程中,这样的话就可以不用占用主线程,等这个线程执行完了,再通知主线程更新界面。

代码改动后,变成这样:

        Executors.newSingleThreadExecutor().submit(() -> {
            for (double i = 0; i < 100.0; i++) {
                
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                   
                final double p = i;
                Platform.runLater(() -> {
                    progressBar0.setProgress(p / 100);
                    
                });
            }
        });

 这里使用了Excutors工具类(JDK1.5以后引入),使用方法和new Thread()大同小异,只不过Executors里默认使用线程池,可以降低不必要的线程开销。

运行效果:

 

使用线程之后

 多线程的数据同步

我们对两个进度条进行计数,最上边的标签显示总和,下边一个标签对应一个进度条。结果中可以发现,两个进度条的计数是正确的,但总和却错了。

 

 我们看下进度1的代码(进度2的代码相同,仅仅标签和进度条的id不一样):

        Executors.newSingleThreadExecutor().submit(() -> {
            for (double i = 0; i <= 100.0; i++) {
                    var c = count;
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    c++;
                    count = c;
                final double p = i;
                Platform.runLater(() -> {
                    progressBar0.setProgress(p / 100);
                    label0.setText(count + "");
                    label1.setText(p + "");
                });
            }
        });

count是个全局变量,两个线程同时向这个变量赋值。因为中间有一个sleep()操作,所以在取得count的值之后,并不能确保count没有发生改变,如果得到count是的时候是1,等sleep()完后,可能是3,而此时还在1的基础上累加,最后将2再赋给count,于是误差就产生了。

为了解决这个问题,我们可以在累加完之前,不允许别的线程去修改count的值,大家共同拥有同一把钥匙,我拿钥匙了,别的就在外边等着。

 我们给计数部分的代码,套一层synchronized 块,此时HelloController.this就是同步锁的钥匙,哪个线程先执行到这个语句,就代表着拿到了钥匙,然后继续执行后边的语句,别的线程则需要停留在synchronized 的行,直到前边的线程已经退出语句块。

                synchronized (HelloController.this) {
                    var c = count;
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    c++;
                    count = c;
                }

 加上同步操作后,我们再下运行效果:

线程同步

 原子变量

对于计数器这东西,我们可以使用原子变量。这样,我们就不需要在自增自减上加锁,可以提升代码的性能。

定义一个整型的原子变量:

private AtomicInteger count = new AtomicInteger(0);

 修改同步代码:

Executors.newSingleThreadExecutor().submit(() -> {
            for (double i = 0; i < 100.0; i++) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                var c = count.incrementAndGet();

                final double p = i;
                Platform.runLater(() -> {
                    progressBar0.setProgress((p + 1) / 100);
                    label0.setText(count + "");
                    label1.setText((p + 1) + "");
                });
            }
        });

 关于死锁

在使用多级锁的时候,容易发生死锁。

死锁示例:

    private Object locker1 = new Object();
    private Object locker2 = new Object();

    @FXML
    protected void onDeadLockButtonClick() {
        new Thread(() -> {
            synchronized (locker1) {
                System.out.println("Thread 1 in locker1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker2) {
                    System.out.println("Thread 1 in locker2");

                }
            }
        }).start();

        new Thread(() -> {
            synchronized (locker2) {
                System.out.println("Thread 2 in locker2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker1) {
                    System.out.println("Thread 2 in locker1");

                }
            }
        }).start();
    }

 运行结果为,点击死锁后,两个线程都不再往下执行,处在锁死状态。

 线程重用

按照计算机操作系统原理,创建线程是有一定的开销的。不停地创建销毁线程,会造成系统性能下降,所以尽量创建少的进程,并反复加以利用。原理是创建一个工作进程,并让其进入等待状态,在需要的时候发出通知,让工作进程再次进入工作。

实现代码:

    private Object locker3 = new Object();

    @FXML
    protected void onReUseThreadButtonClick() {
        synchronized (locker3) {
            locker3.notify();
        }
    }

    @FXML
    protected void onCreateThreadButtonClick() {
        new Thread(() -> {
            logs.appendText("Thread created...\n");
            synchronized (locker3) {
                while (true) {
                    try {
                        logs.appendText("Thread waiting...\n");
                        locker3.wait();
                        logs.appendText("All right. Next...\n");
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();
    }

 运行效果:

 线程的汇集操作

可以将一些线程并行计算的结果,汇总到同一个线程。假如电脑的CPU核心数和线程数较多,可以拆分对应个数的线程进行并行计算,再将结果汇集到主线程。原理使用Thread.join()方法。

代码:

        new Thread(() -> {
            Thread t1 = new Thread(() -> {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                Platform.runLater(() -> {
                    logs.appendText("t1 exited....\n");
                });
            });
            t1.start();
            Thread t2 = new Thread(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                Platform.runLater(() -> {
                    logs.appendText("t2 exited....\n");
                });
            });
            t2.start();
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            Platform.runLater(() -> {
                logs.appendText("current exited....\n");
            });
        }).start();

 运行效果:

 

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

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

相关文章

类文件结构和初识一些字节码指令

文章目录类文件的结构为什么要了解字节码指令Class文件结构Java虚拟机规定的类结构魔数版本常量池访问标志类索引、父类索引、接口索引Ⅰ. interfaces_count&#xff08;接口计数器&#xff09;Ⅱ. interfaces[]&#xff08;接口索引集合&#xff09;字段表集合**1. 字段表访问…

【React】使用 react-pdf 将数据渲染为pdf并提供下载

文章目录前言环境步骤1. 安装react脚手架2. 使用 create-react-app 创建项目 &#xff08;首字母不要大写、不要使用特殊字符&#xff09;3. 用 vscode 打开目录 react-staging4. yarn 启动项目5. 参考 react-pdf readme加入依赖6. 结合 github readme 和官方文档产出 demo 代码…

OpenGL 色彩替换

目录 一.OpenGL 色彩替换 1.IOS Object-C 版本1.Windows OpenGL ES 版本2.Windows OpenGL 版本 二.OpenGL 色彩替换 GLSL Shader三.猜你喜欢 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 基础 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录…

防抖debounce与节流throttle(63rd)

一、前言 当用户高频触发某一事件时&#xff0c;如窗口的resize、scroll&#xff0c;输入框内容校验等&#xff0c;此时这些事件调用函数的频率如果没有限制&#xff0c;可能会导致响应跟不上触发&#xff0c;出现页面卡顿&#xff0c;假死现象。此时&#xff0c;我们可以采用…

深度剖析NIKE Web3平台:为什么Web3对品牌很重要?

欢迎关注沉睡者IT&#xff0c;点上面关注我 ↑ ↑ 上周&#xff0c;NIKE 宣布了其新的 Web 3 平台 .SWOOSH&#xff0c;这是 NIKE Virtual Sudios (耐克虚拟工作室) 的一项新举措&#xff0c;将成为 NIKE 所有数字资产创作的“大本营”。继去年收购 RTFKT 之后&#xff0c;此次…

SpringBoot SpringBoot 原理篇 3 核心原理 3.4 启动流程【3】

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 原理篇 文章目录SpringBootSpringBoot 原理篇3 核心原理3.4 启动流程【3】3.4.1 看源码咯3 核心原理 3.4 启动流程【3】 …

负载均衡反向代理下的webshell上传

目录 架构如下&#xff1a; 实验环境&#xff1a; AntSword-Labshttps://github.com/AntSwordProject/AntSword-Labs 搭建环境&#xff1a; 启动环境&#xff1a; 测试连接&#xff1a; 地址不停的在漂移会造成的问题&#xff1a; 难点一&#xff1a;我们需要在每一台节点…

特征工程(六)—(2)利用LDA进行特征转换

1、LDA的手动处理 LDA&#xff08;线性判别分析&#xff09;是特征变换算法&#xff0c;也是有监督分类器。 和PCA一样&#xff0c;LDA的目标是提取一个新的坐标系&#xff0c;将原始的数据集投影到一个低维的空间中。 和PCA的主要区别是&#xff0c;LDA不会专注数据的方差&a…

[附源码]计算机毕业设计JAVA实验教学过程管理平台

[附源码]计算机毕业设计JAVA实验教学过程管理平台 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

【Hack The Box】linux练习-- Magic

HTB 学习笔记 【Hack The Box】linux练习-- Magic &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月21日&#x1f334; &#x1f36…

Js逆向教程17-极验滑块 实现加密算法的逻辑

Js逆向教程17-极验滑块 实现加密算法的逻辑 还是和上节课一样&#xff0c;针对这个网址 https://www.geetest.com/demo/slide-float.html 一、加密算法的结果查看 计算u运行后的结果&#xff1a; a45a0551c344b03be428cab551f9755f073e64061c35988a29d6ba70e7d35c8b9e963b63…

全波形反演的深度学习方法: 第二章 正演 (草稿)

本章介绍正演的基础知识. 本贴的目的是进行内部培训, 错误之处较多, 希望不要误导读者. 2.1 弦线波动基本原理 波动方程是正演的基础. 最简单的模型是在一根弦上的波动, 假设如下: 横震动. 例如拨动吉他弦;微小震动. 满足 u(xΔx,t)−u(x,t)≪Δxu(x \Delta x, t) - u(x, t…

Redis学习(三)之 分布式锁详解

1、redis分布式锁相关的可以移步这篇文章redis做分布式锁实战案例详解_酒书的博客-CSDN博客 这里是对该篇文章的加深与补充 2.集群主从切换导致锁丢失问题&#xff1a;在redis主从架构中&#xff0c;写入都是写入到主redis中&#xff0c;主redis会同步数据到slave机器&#x…

Mybatis插件机制

什么是插件机制 插件插件&#xff0c; 就是能在执行某个方法之前加入一些功能代码&#xff0c; 有啥方法能够实现呢&#xff1f;当然是动态代理了&#xff0c; 为啥要使用动态代理应为他是为了写框架扩展性必备的东西。 只要定义一些接口 或者类 就行使用jdk自带的或者CGLIB之…

分布式NoSQL数据库HBase实践与原理剖析(二)

title: HBase系列 第五章 HBase核心原理 5.1 系统架构 注意&#xff0c;其实上图中的HLog应该在HRegionServer里面&#xff0c;而不是在HRegion里面。所以图有点点问题。其实通过后面的物理存储的图也能发现这个问题。 Client 职责 1、HBase 有两张特殊表&#xff1a; .meta.…

力扣 21. 合并两个有序链表 C语言实现

题目描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 题目链接 方法1&#xff1a;遍历 新建一个链表 newList 用于存放合并后的链表&#xff0c;设置一个指针指向该链表最后一个位置的 next&#xff0c…

77.【JavaWeb文件上传和邮件发送04】

JavaWeb(二十五)、文件上传1.准备工作2.实用类介绍3.思维导图:4.正戏开始5.完整代码(二十六)、邮箱发送1.邮箱发送的原理:2.服务器的原理3.下载两个jar包4.基本类:5.全部代码(二十七)、网站注册发送邮件实现(二十五)、文件上传 1.首先创建一个empty项目 2.配置project项目中的…

【JVM】jvm中的栈简介

jvm中的栈简介一、JVM体系结构二、栈是什么&#xff1f;三、栈的特性四、栈帧五、栈的运行原理5.1 运行原理5.2 代码示例5.2.1 方法的入栈和出栈5.2.2 没有捕获异常5.2.3 捕获异常六、栈帧的内部结构七、运行时数据区&#xff0c;哪些部分存在Error和GC&#xff1f;八、本文源码…

boot 创建 https

需要在配置文件中&#xff1a;加入 server:ssl:key-store: classpath:https.keystorekey-store-type: JKSkey-alias: tomcatkey-password: 123456key-store-password: 123456port: 8089 这样原本请求的http&#xff0c;就需要变成https&#xff0c;其他类似 RestController p…

深度学习入门(五十六)循环神经网络——循环神经网络RNN

深度学习入门&#xff08;五十六&#xff09;循环神经网络——循环神经网络RNN前言循环神经网络——循环神经网络RNN课件潜变量自回归模型循环神经网络使用循环神经网络的语言模型困惑度&#xff08;perplexity&#xff09;梯度裁剪更多的应用RNNs总结教材1 无隐状态的神经网络…