Java多线程基础汇总(上)

目录

一. 概念

 二.线程的创建

三. Thread类的常见方法

1.启动一个线程

2.终止一个线程

3.等待一个线程

四. 线程安全问题

1.导致线程安全的原因:

 2.如何解决线程安全问题

2.1  synchronized关键字

2.2  volatile关键字

3. wait 和 notify

4.wait 和 sleep的区别(面试题)



一. 概念

     Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

     线程:一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行着多份代码。通俗点说就是一个应用程序中执行着不同的功能,比如我们用微信聊天,微信运行起来相当于一个进程,而我们使用微信聊天,发朋友圈等等就相当于不同的线程。

     进程:是正在运行的程序的实例,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,通俗点说相当于一个跑起来的程序。

     举个例子:就拿吃饭这件事来说,我们在吃饭的同时,可以有玩手机,看电视,聊天等等一系列的行为,我们把吃饭这件事就可以看做是一个进程,而玩手机,看电视,聊天这样的行为可以看做是一个独立的线程,当这些行为一起进行的时候,我们就可以看做是一个多线程任务


 二.线程的创建

转载我之前的blog:Java线程的创建_Bc_小徐的博客-CSDN博客


三. Thread类的常见方法

1.启动一个线程

如果要启动一个线程,我们调用Thread类的start方法来解决

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程1");
        });
        //启动线程
        thread.start();
        System.out.println("线程2");
    }

当我们调用start方法后,这个才真正的在操作系统的底层创建出了一个线程,这个线程就进入就绪状态了,等待cpu的调度;

2.终止一个线程

在多线程中,我们通常通过设置一个标志位来终结一个线程的运行

class Mythread implements Runnable{
    //设置一个标志位,通过改变标志位的值,来终止线程的进行
    public boolean flag = true;
    @Override
    public void run() {
        while (flag){
            for (int i = 0; i < 999; i++) {
                System.out.println(i);
            }
        }
    }
}
public class Demo3 {
    public static void main(String[] args) {
      Mythread mythread = new Mythread();
      Thread thread = new Thread(mythread);
        thread.start();
        for (int i = 0; i < 999; i++) {
            System.out.println(i);
            if(i == 666){
              //改变标志位的值
                mythread.flag = false;
                System.out.println("该线程该停止了");
            }
        }
    }
}

3.等待一个线程

在线程里面,有的时候我们需要等待一个线程先执行完,再进行下一个线程的进行,此时,Thread标准库里提供了一个join方法,让我们去实现:

public class Demo4 {
    //等待一个线程
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("线程1");

        });
        //启动线程
        thread.start();
        //在main函数里调用join,意思是让main线程等待thread线程执行完,main线程才执行
        thread.join();
        System.out.println("主线程");
    }
}

 

从执行结果我们可以看到,这里在main函数里调用了join方法,意思在让thread线程先执行,执行完主线程才开始执行, 那如果thread线程一直没有执行完,那么主线程会处于一个叫阻塞等待的状态,也不会参与CPU的调度,直到thread线程执行完,阻塞才会解除,继续执行;


四. 线程安全问题

1.导致线程安全的原因:

关于线程安全的问题我认为在Java中一直是个重要的问题,我们写程序一直要确保万无一失,关于线程安全可以举个例子来看:

class Counter {
    public int count = 0;

    public void add() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.add();
            }

        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.add();
            }

        });
        //启动线程
        thread1.start();
        thread2.start();

        //等待线程
        thread1.join();
        thread2.join();

        System.out.println(counter.getCount());
    }

上述代码中,我们利用线程来对count 进行++的操作,并且让thread1和thread2线程各循环1000次,如果按照正常的逻辑执行的话,我们最后输出的一个结果是2000,但是真正输出的结果并不是这样的,如下图可以看到,我们分别打印了3次,而每次的结果都是不同的,所以

 

 


这里就涉及到线程安全的问题了,出现这种Bug的原因实际上主要和线程调度的随机性有关,针对这个count++的操作,站在CPU的角度分析,是相当于三条指令来进行完成的:

第一步:Load操作:把内存中的数据读取到CPU寄存器中

第二步:Add:把寄存器的值进行+1的运算

第三步:Save:把寄存器的值写回到内存中

这三个步骤在两个线程中并发也是随机进行的,相当于线程的调度是随机的,它是一个大的范围,而对于++这个操作,这三条指令也是随机执行的,它在大的范围之下的小的范围,这就导致了为什么最终的结果和我们预期的不一样;

上述的每一个系列号都是一种情况:

   拿第二个图来说,thread1 的 load,add,save先执行,当thread1的三条指令全部执行完,此时就进行了一个加1的操作,thread1执行完,再执行thread2 的三条指令;

   拿第三个图来说,thread1 的 load 指令先执行,再执行 thread2 的loaf add save 的操作,此时 thread1 的加1操作并没有执行完,所以不会加1,当执行完 thread2 的三条操作后,再执行thread1的剩下两条指令(add,save),当这两条指令执行完之后,才是一个完整的加1操作;

这也是导致线程安全的主要原因:它不是按顺序执行的,而是随机调度的;


 2.如何解决线程安全问题

2.1  synchronized关键字

    上述的线程不安全问题,主要就是三条指令随机调度导致的,为了解决这一问题,我们可以对其加锁操作,将这三条指令的操作封装起来,意思就是要执行就一起执行,不能分散的执行,这样也是确保了操作的原子性,使得即使2个线程并发执行,但是要执行的操作是原子的,所谓原子,就是不可分割的;

Java里对于加锁的操作是使用synchronized关键字:

在被synchronzied修饰的代码块中,会触发加锁,出了synchronized代码块,就会解锁;

public void add() {
        synchronized (this){
            count++;
        }
    }

 关于synchronized的写法:

如果对于普通方法:

第一种写法,锁住的对象就是当前的操作
public void add() {
        synchronized (this){
            count++;
        }

    }
第二种写法,直接修饰这个方法

synchronized public void add(){
        count++;
    }

如果对于静态成员方法:

当修饰静态方法的时候
第一种写法,synchronized后面锁住的对象就是当前类对象
public static void add(){
        synchronized (Counter.class){

        }
    }
第二种写法,直接修饰这个静态方法    
synchronized public static void add(){
        
    }

2.2  volatile关键字

导致线程安全的原因有很多,不止是原子性这一方面,例如下面一段代码:

public class Demo6 {
    private static int ret = 0;

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            while (ret == 0) {

            }
            System.out.println("线程结束");
        });
        Thread thread2 = new Thread(() -> {
            Scanner scan = new Scanner(System.in);
            ret = scan.nextInt();
        });
        thread1.start();
        thread2.start();
    }
}

上述代码中,我们通过输入ret的值来结束线程的进行,当我们输入一个非零的数,按照代码的逻辑,就可以结束线程1,打印线程结束了,但是预期的结果和我们实际输出的结果并不一样,当我们运行程序可以看到:

线程始终没有结束,并且也没有打印线程结束,导致这一原因就是编译器优化做的决策,

 所以这里相当于无论你怎么输入,它始终与第一次拿到的值进行比较,为了解决这一问题,我们就要使用volatile关键字来解除编译器优化,以确保拿到的值是你下一次输入的值,而不是和第一次拿到的值进行比较了;

加上volatile关键字之后,输出的结果就和我们预期的一样了,被volatile修饰的变量就禁止编译器优化,保证每次都是从内存中重新读取数据;

注:但是volatile 不能保证原子性,它使用的场景是一个线程读,一个线程写;


3. wait 和 notify

     由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知,但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序,此时wait和notify的作用就来了,我们可以通过这两个关键字来控制线程的先后进行:

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread thread1 = new Thread(() -> {

            try {
                System.out.println("线程1中的wait开始");
                synchronized (locker) {
                    locker.wait();
                }
                System.out.println("线程1中的wait结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        //启动线程
        thread1.start();
        //线程休眠
        Thread.sleep(1000);

        Thread thread2 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("线程2中的notify开始");
                locker.notify();
                System.out.println("线程2中的notify结束");
            }
        });
        //启动线程
        thread2.start();
    }
}

上述代码中我们就使用了wait和notify这两个关键字,让线程1先启动,再使用wait让线程1进入阻塞状态,再执行线程2,等线程2执行完,notify会起到一个唤醒的作用,唤醒线程1,让线程1执行完,这样就灵活的控制了线程执行的顺序;


wait主要做的事有:

1.解锁(所以在使用wait之前,要对其进行加锁,不然怎么解锁)

2.进入阻塞状态

3.等待被唤醒,重新拿到锁

notify的作用就是唤醒等待的方法

4.wait 和 sleep的区别(面试题)

1.wait需要搭配 synchronized 使用,而 sleep 不需要

2.wait 是 object 的方法,sleep 是 Thread的方法

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

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

相关文章

你写的C语言代码被翻译成可执行程序,需要这几步

本篇博客会讲解C语言的灵魂知识点&#xff1a;你写出来的C语言代码究竟是如何让计算机识别并且执行的。C语言是一门计算机语言&#xff0c;可以方便程序员和计算机沟通&#xff0c;但是&#xff0c;计算机只认得二进制&#xff0c;怎么会认得你写的C语言代码是什么意思呢&#…

【ArcGIS Pro二次开发】(12):txt文件和Excel文件的读写

在Arcgis Pro的工作流中&#xff0c;数据的输入是很常见的。这里以TXT和Excel两种文件为例&#xff0c;在SDK中实现数据的读取和写入。 一、txt文件的读写 txt文件的读写相对简单&#xff0c;可以用Arcgis Pro自带的OpenItemDialog打开txt文件&#xff0c;并直接读取&#xff…

Java稀疏数组的应用

文章目录需求存储结构分析问题稀疏数组稀疏数组存储结构整体思路代码示例需求 编写一个五子棋程序&#xff0c;可以完成存盘退出和继续上局的功能。这时就会涉及到棋盘当前棋子状态数据的保存和读取 黑色棋子为&#xff1a;1&#xff0c;白色棋子为&#xff1a;2&#xff0c;0…

BERT: Pre-training of Deep Bidirectional Transformers forLanguage Understanding

参考BERT原文[1810.04805] BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding (arxiv.org)【(强推)李宏毅2021/2022春机器学习课程】 https://www.bilibili.com/video/BV1Wv411h7kN/?p73&share_sourcecopy_web&vd_source30e93e9c70e…

Less 运行环境

文章目录Less 运行环境概述运行Less方式一&#xff1a;浏览器环境方式二&#xff1a;koala编译器方式四&#xff1a;Node环境下编译Less 运行环境 概述 Less &#xff08;Leaner Style Sheets 的缩写&#xff09; 是一门向后兼容的 CSS 扩展语言。这里呈现的是 Less 的官方文…

ChatGPT能够干翻谷歌吗?

目前大多数人对于ChatGPT的喜爱&#xff0c;主要源自于其强大的沟通能力&#xff0c;当我们向ChatGPT提出问题时&#xff0c;它不仅能够为我们提供结论&#xff0c;而且还能够与我们建立沟通&#xff0c;向ChatGPT提出任何问题&#xff0c;感觉都像是在与一个真实的人类进行交谈…

蓝桥杯备考

数论&#xff1a;判断素数&#xff0c;鸽笼定理&#xff0c;抽屉理论 注意事项&#xff1a; 组合剪枝&#xff1a;i < n - (k - path.size()) 1 long类型的数后面要加L long s 2658417853L; 保留几位小数&#xff1a; System.out.printf(“%.2f”, arg); 四舍五入问题…

【Python】如何实现Redis构造简易客户端(教程在这)

文章目录前言一、准备二、原理剖析三、编写简易Redis客户端总结前言 Redis 是我们在开发过程中经常会用到的内存数据库&#xff0c;尤其是在Python的第三方模块Redis-py的支持下&#xff0c;在Python中使用Redis及其方便。 但是在有些情况下&#xff0c;我们无法使用像Redis-…

学习 Python 之 Pygame 开发魂斗罗(十四)

学习 Python 之 Pygame 开发魂斗罗&#xff08;十四&#xff09;继续编写魂斗罗1. 创建桥类2. 在主类中加入一些类变量3. 显示桥4. 解决玩家与桥的碰撞体问题5. 解决敌人与桥的碰撞体问题继续编写魂斗罗 在上次的博客学习 Python 之 Pygame 开发魂斗罗&#xff08;十三&#x…

Visual Studio Code 1.77 发布,扩展的 GitHub Copilot 集成

VS Code 1.77 已发布&#xff0c;此版本一些主要亮点包括&#xff1a; Accessibility 改进 - 用于悬停、通知和 Sticky Scroll 的新键盘快捷键。 、down、home、end、page up和page down 键来聚焦悬停控件并进行水平和垂直滚动。聚焦悬停控件的键盘快捷键(CtrlK CtrlI)与用于在…

ArduPilot飞控之DIY-F450计划

ArduPilot飞控之DIY-F450计划1. 历史2. 源由3. 计划3.1 硬件3.2 软件4. 动手4.1 接线4.1.1 ELRS nano接收机4.1.2 BN880 GPS模块4.1.3 Radio Telemetry4.2 配置4.2.1 选择四轴机型4.2.2 电源参数调整4.2.3 校准加速度计4.2.4 校准磁力计4.2.5 遥控器校准4.2.6 电机设置4.2.7 电…

Linux- 系统随你玩之--玩出花活的命令浏览器上

文章目录1、背景2、命令浏览器2.1、命令浏览器介绍2.2、特点2.3 常用功能选项3、实操3.1、使用 wget 下载文件3.2、 断点续传3.3、镜像整个站点4、 总结1、背景 一位友人说他有台服务器&#xff0c;需要下载一个文件&#xff0c;但是没有视窗界面与下载工具&#xff0c;怎么办…

360周鸿祎离婚老婆能分得90亿,如果奶茶妹妹离婚会不会分走更多?

‍数据智能产业创新服务媒体——聚焦数智 改变商业最近&#xff0c;中国互联网界又有一个新鲜的大瓜——360周鸿祎与其老婆离婚&#xff0c;对方分走了近90亿。根据360发布的公告&#xff0c;董事长周鸿祎与胡欢经友好协商&#xff0c;已办理解除婚姻关系手续&#xff0c;并就…

不敲代码用ChatGPT开发一个App

先说下背景&#xff0c;有一天我在想 ChatGPT 对于成熟的开发者来说已经是一个非常靠谱的助手了&#xff0c;身边也确实有很多同事把它作为一个离不开的助理担当。 但是如果我只是略微懂一点前端知识的新人&#xff0c;了解 HTML、CSS、JS 相关的知识&#xff0c;想开发一个安…

智慧水务信息化平台建设,实现供水一体化管控

平台概述 柳林智慧水务系统平台是以物联感知技术、大数据、智能控制、云计算、人工智能、数字孪生、AI算法、虚拟现实技术为核心&#xff0c;以监测仪表、通讯网络、数据库系统、数据中台、模型软件、前台展示、智慧运维等产品体系为支撑&#xff0c;以城市水资源、水生态、水…

技术分享| 什么是动态更新?

近期工作提到动态更新比较多&#xff0c;今天也借此机会&#xff0c;梳理一下相关的机制原理同大家分享。 动态机制及技术原理 动态研发模式就是一种基于云端的移动应用开发方法&#xff0c;主要能让开发者快速构建和发布多端的移动应用&#xff0c;实现业务的敏捷迭代和热更…

自动化篇 | 13 | app自动化:airtest

1 airtes简介 1.1 参考地址 http://airtest.netease.com/ # airtest官网 https://airtest.doc.io.netease.com/ # airtest操作方法 https://airtest.doc.io.netease.com/IDEdocs/faq/1_code_examples/ # 代码示例2 airtest架构 Airtest框架 3 airtest使用 3.1 打开界…

深度学习 - PyTorch入门

PyTorch入门前言张量Tensor导入torch创建张量返回numpy.ndarraytensor与list/ndarray/dataframe转化&#xff1a;tensor内数据类型转化维度变换0维item()&#xff1a;reshape()&#xff1a;squeeze&unsqueeze&#xff1a;permute&#xff1a;张量相关操作item()zeros() &am…

十二星座,各适合骑什么牌子的自行车

很多骑友喜欢研究星座&#xff0c;但并不大明白自己是什么星座&#xff0c;什么属性什么系&#xff0c;更不明白适合骑哪款自行车&#xff0c;下面大致说一下。处女座&#xff1a;适合骑共享单车&#xff0c;以黑色为主色调。摩羯座&#xff1a;适合骑提安特自行车&#xff0c;…

[Python] 循环语句

循环语句就是在符合条件的情况下&#xff0c;重复执行一个代码段 1.while循环 while语句可用于在条件为真时反复执行代码块 语法格式 while 条件语句:执行语句 当条件语句为真(True)时&#xff0c;就会执行while循环下的语句 示例 实现1到100 的累加并输出求和结果 …
最新文章