多线程——概念及线程安全

news2024/12/26 0:07:03

文章目录

  • 多线程
    • 概念
    • 进程vs线程
    • 多线程的优势/好处/使用场景
    • 线程的状态
    • 创建线程的方式
    • 线程的启动
      • Thread中,start()和run()有什么区别?
    • Thread类中的常用方法
      • join()
      • 获取当前线程引用
      • 线程休眠
    • 线程中断
    • 线程的属性
    • 多线程效率
    • 局部变量在多线程中的使用
  • 线程安全问题
    • 1.什么情况会产生线程不安全
    • 2.线程不安全的原因是什么
    • 如何解决线程不安全的问题
      • 解决线程安全的方式一: synchronized关键字
      • 解决线程安全的方式二: volatile关键字

多线程

概念

钱程的实现万式有多种:
(1)内核线程=>Java中的线程,是基于内核线程的轻量级实现(简单的说:轻量级进程)
(2)用户线程: 由程序自己来实现线程,包括线程的调度等等

进程vs线程

进程和线程的关系:

  1. 多个进程内存是隔离开的,一个进程中的多个线程,可以共享内存(进程包含线程)
  2. 进程是系统分配资源的最小单位;线程是系统调度cpu执行的最小单位。
  3. 线程的创建、销毁代价比进程小(状态转换进程的开销大于线程)
  4. 线程(由bug)可能会造成整个进程挂掉;进程间是独立运行(可能存在进程通信)

多线程的优势/好处/使用场景

  • 充分利用CPU资源,提高执行效率。
  • io等阻塞时(如果希望能同时接受数据)

缺陷、注意:

  • 线程的创建和销毁也是具有一定的系统开销:一般用于执行耗时比较长的任务。
  • 增加编码的复杂程度:执行顺序、线程安全问题。

线程的状态

在这里插入图片描述
java线程的状态
在这里插入图片描述

创建线程的方式

在这里插入图片描述
1.继承Thread

public class 继承Thread {
    public static void main(String[] args) {
        //继承的写法:1.定义一个类来继承Thread
        //创建一个线程
        MyThread myThread = new MyThread();
        //运行一个线程:申请系统调度运行
        myThread.start();

        //继承的写法 2.使用一个匿名内部类
        Thread t=new Thread(){
            @Override
            public void run() {
                System.out.println("匿名内部类 run");
            }
        };
    }

    //继承Thread;重写run方法
    private static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("MyThread run");
        }
    }
}

2.实现Runnable接口

public class 实现Runnable {
    public static void main(String[] args) {
        Thread t1=new Thread(new MyRunnable());
        t1.start();

        //实现Runnable写法2. 匿名内部类
        Runnable r=new Runnable(){
            @Override
            public void run() {
                System.out.println("匿名内部类 run");
            }
        };
        Thread t2=new Thread(r);
        t2.start();

        //变形 也可把匿名内部类对象,直接写在构造方法参数上
        Thread t3=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类run");
            }
        });
        t3.start();
    }
    private static  class MyRunnable implements Runnable{

        @Override
        public void run() {
            System.out.println("MyRunnable run");
        }
    }
}

线程的启动

thread.start() =>申请系统调度,执行thread中的任务(重写的run方法)

Thread中,start()和run()有什么区别?

(1) start:启动线程的方式
(2) run:属于线程任务的描述

通过代码进一步分析其区别:

public class StartVsRun {
    /**
     * java StartVsRun:
     * 1.执行java.exe进程,分配内存
     * 2.创建java虚拟机,执行StartVsRun类加载
     * 3.创建一个main线程,执行StartVsRun.main()
     * @param args
     */
    public static void main(String[] args) {
//        while(true){
//
//        }

        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){}
            }
        },"t线程");
        t.start();
    }
}

main线程创建Thread的代码,到底执行了哪些代码:没有执行run
在这里插入图片描述

在这里插入图片描述
多个线程,同时存在并发和并行的现象

即:以下代码打印语句结果顺序是无序的。main线程和t线程存在并发和并行。
在这里插入图片描述
在这里插入图片描述

Thread类中的常用方法

join()

使用join后,两个线程并发并行随机执行的方式,就变为有一定顺序

在这里插入图片描述
代码:

public class Threadjoin {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
//                System.out.println("t线程 run");
                while(true){}
            }
        },"t线程");

        //当前线程(t.join在哪个线程执行,就是谁)等待,直到线程引用执行完毕
        t.start();
//        t.join();//main 等待,
        t.join(3000);//不注释:t线程先打印,然后一直运行; main线程等3秒后打印
        System.out.println("t线程 start,这里是main");
    }
}

获取当前线程引用

一般上下文语义,是描述某一行,说当前线程——某行代码所在的线程
在这里插入图片描述

public class CurrentThread {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //在哪个线程中,执行,currentThread返回的就是哪个线程
                System.out.println(Thread.currentThread().getName());
            }
        }, "子线程").start();
        System.out.println(Thread.currentThread().getName());
        //打印结果
        //main
        //子线程
    }
}

线程休眠

调试代码可用sleep()方法调试
在这里插入图片描述

public class Sleep {

    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    System.out.println(Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

线程中断

自定义标志位,能够实现某些条件的中断
但如果线程处于等待/超时等待/阻塞,就没法中断

public class 自定义标志位 {

    //设计一个标志位:表示是否被中断
    private static boolean isInterrupt=false;

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                //循环10次。每次打印次数,休眠1s
                try {
                    for(int i=0;i<10&&!isInterrupt;i++){
                        System.out.println(i);
                        //自定义标志位,能够实现某些条件的中断
                        //但如果线程处于等待/超时等待/阻塞,就没法中断
                        //修改时间为100s,就无法中断
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        isInterrupt=true;
    }
}

在这里插入图片描述

public class Interrupt {
    //设计一个标志位:表示是否被中断
    private static boolean isInterrupt=false;

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                //循环10次。每次打印次数,休眠1s

                //获取当前线程的中断标志位:这样写,只是获取到一个不变标志位
//                    boolean isInterrupt=Thread.currentThread().isInterrupted();
//                    for(int i=0;i<10&&!isInterrupt;i++){
                try {
                    while (!Thread.currentThread().isInterrupted()) {
                        for (int i = 0; i < 10; i++) {
                            System.out.println(i);
                            Thread.sleep(1000);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }});
        t.start();
        Thread.sleep(3000);
        t.interrupt();
    }
}

注意:
(1) Java中断,是以中断标志位的方式来执行
(2) interrupt是发起中断的动作,但线程是否中断,由自己代码是否判断标志位来决定
(3)线程如果处于某些等待/超时等待的方式(会显示的抛InterruptedException),都是允许被中断
中断的方式是:1.抛异常来中断⒉抛异常以后,会重置/还原中断标志位

线程的属性

public class 获取线程属性 {

    public static void main(String[] args) {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){}
            }
        });
        t.setDaemon(true);//为后台线程
        t.start();
        System.out.println(t.getId());
        System.out.println(t.getName());
        System.out.println(t.getState());//获取线程状态
        System.out.println(t.getPriority());
        System.out.println(t.isDaemon());//是否后台线程
        System.out.println(t.isAlive());//是否存活,如果

        //后台线程:java进程,至少有一个非后台线程(用户线程)存活,进程才不会退出
        //如果把t线程在启动前,设为后台线程,进程不会管t线程是否执行完

    }
}

多线程效率

执行一段任务,如何确定多线程的线程数量?

由于系统中,可能有很多需要耗费cpu资源的进程,可能对本java进程造成影响
我们这里探讨的前提:系统内存满足java进程,希望Java进程执行的任务能充分利用cpu资源(占满)
在这里插入图片描述

局部变量在多线程中的使用

局部变量在另一个线程无法使用,为什么?

public class Demo {

    //静态变量i:方法区;   0:Integer缓存(堆)
    private static int i = 0;
    //常量i2: 方法区; 1:同上
    private static final int i2 = 1;
    //静态变量o:方法区,   new Object(堆)
    private static Object o = new Object();

    //实例变量i3:和对象生命周期一样; 3:同上
    private int i3 = 3;

    public static void main(String[] args) {
        //x: 栈,3: 栈
        int x = 3;
        //ox: 栈; new Object堆
        Object ox = new Object();
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(x);
//                x = 4;
            }
        });
        t.start();

        for (int j = 0; j < 10; j++) {
            Thread tt = new Thread(new Runnable() {
                @Override
                public void run() {
//                    System.out.println(j);
                }
            });
            tt.start();
        }
    }
}

局部变量是属于线程私有的
在这里插入图片描述

线程安全问题

加里多线程情况下代码运行的结果符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

1.什么情况会产生线程不安全

在这里插入图片描述

2.线程不安全的原因是什么

在这里插入图片描述

如何解决线程不安全的问题

(1)某个线程,对共享变量的写操作,可以加锁来保证线程安全

在这里插入图片描述

(2)如果某个线程,对共享变量是读操作,使用volatile关键字就可以保证线程安全

在这里插入图片描述

如果对共享变量的写操作,不依赖任何共享变量,也可以使用volatile保证线程安全
在这里插入图片描述

解决线程安全的方式一: synchronized关键字

在这里插入图片描述

在这里插入图片描述
synchronized作用:

多个线程,使用同一个对象进行加锁(基于对象头加锁),可以实现线程间的同步互斥

同步: 多个线程,按序执行同步代码(synchronized花括号的部分)
互斥: 多个线程间,执行同步代码具有互斥的特点

注意:如果不同的线程,是申请不同对象的锁,就没有同步互斥的作用(还能并发并行的执行)

synchronized保证原子性、可见性、有序性
互斥:就能满足原子性(某个线程执行同一个对象加锁的同步代码,排斥另一个线程加锁,满足最小执行单位)
刷新内存: synchronized结束释放锁,会把工作内存中的数据刷新到主存;其他线程申请锁,获取的始终是最新的数据满足可见性)
有序性:某个线程执行一段同步代码,不管如何重排序,过程中不可能有其他线程执行的指令
这样多个线程执行同步代码,就是满足一定的顺序(大家依次执行)

线程安全版代码

两个变量对同一变量执行++操作。

public class synchronized线程安全 {

    private static int count;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    synchronized (synchronized线程安全.class) {
                        count++;
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    synchronized (synchronized线程安全.class) {
                        count++;
                    }
                }
            }
        });
        t1.start();
        t2.start();
        //main线程等待t1,t2都执行完
        t1.join();
        t2.join();
        //看看count的值
        System.out.println(count);
    }
}

在这里插入图片描述

解决线程安全的方式二: volatile关键字

语法
修饰某个变量(实例变量,静态变量)

在这里插入图片描述
作用
(1) 保证可见性:多个线程对同一个变量的操作,具有可见性
(2)禁止指令重排序,建立内存屏障

使用场景:
共享变量的读操作,及常量赋值操作(原因:这些操作本身就具有原子性)

某些即存在读,也存在写的操作:
简单的满足线程安全:就是整个代码全部加锁–效率会稍微低一点
优化方案:对共享变量的读使用volatile保证线程安全,写操作加锁——满足线程安全,效率也高

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

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

相关文章

replit搭建

本文章用于快速搭建“出去”的节点&#xff0c;很简单 每个月只有100G流量中间可能会停止运行&#xff0c;需要手动进入项目开启 1、需要注册一个Replit账号 点击注册 支持Github登录&#xff0c;其他登录也行 2、使用这个模板项目 随便起个名字 3、运行 进行完第二步&am…

【开源项目】第三方登录框架JustAuth入门使用和源码分析

第三方登录框架JustAuth入门使用和源码分析 项目介绍 JustAuth&#xff0c;如你所见&#xff0c;它仅仅是一个第三方授权登录的工具类库&#xff0c;它可以让我们脱离繁琐的第三方登录 SDK&#xff0c;让登录变得So easy! JustAuth 集成了诸如&#xff1a;Github、Gitee、支付…

九、kubernetes中Namespace详解、实例

1、概述 Namespace是kubernetes系统中的一种非常重要资源&#xff0c;它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离。 默认情况下&#xff0c;kubernetes集群中的所有的Pod都是可以相互访问的。但是在实际中&#xff0c;可能不想让两个Pod之间进行互相的访…

花费数小时,带你学透Java数组,这些常用方法你还记得吗?

推荐学习专栏&#xff1a;Java 编程进阶之路【从入门到精通】 文章目录1. 数组2. 一维数组2.1 声明2.2 初始化2.3 使用3. 二维数组3.1 声明3.2 初始化3.3 使用4. 数组在内存中的分布5. 数组常用的方法5.1 Arrays.toString方法5.2 Arrays.copyOf方法5.3 Arrays.copyOfRange方法5…

麦克斯韦(Maxwell)方程组的由来

美国著名物理学家理查德费曼&#xff08;Richard Feynman&#xff09;曾预言&#xff1a;“人类历史从长远看&#xff0c;好比说到一万年以后看回来&#xff0c;19世纪最举足轻重的毫无疑问就是麦克斯韦发现了电动力学定律。” 这个预言或许对吧。可是费曼也知道&#xff0c;麦…

疫情三年划上终止符,好易点却把个人健康写入了产品基因

作者 | 牧之 编辑 | 小沐 出品 | 智哪儿 zhinaer.cn随着12月26日国家卫健委发布的一纸公告&#xff0c;新冠肺炎正式更名为新冠感染。而从次年1月8日起&#xff0c;新冠将被实施「乙类乙管」。同时出入境也将采取开放性政策。这意味着&#xff0c;持续三年的「疫情时期」&#…

大数据技术——HBase简介

文章目录1. HBase定义2. HBase数据模型2.1 逻辑存储结构2.2 HBase 物理存储结构3. HBase基础架构1. HBase定义 HBase – Hadoop Database&#xff0c;是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统&#xff0c;利用HBase技术可在廉价PC Server上搭建起大规模结构化存…

基于BP神经网络的电力负荷预测(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑…

SpringBoot系列之数据库初始化-jpa配置方式

【DB系列】数据库初始化-jpa配置方式 | 一灰灰Blog 上一篇博文介绍如何使用spring.datasource来实现项目启动之后的数据库初始化&#xff0c;本文作为数据库初始化的第二篇&#xff0c;将主要介绍一下&#xff0c;如何使用spring.jpa的配置方式来实现相同的效果 I. 项目搭建 1…

qt windeployqt打包 带多个dll的可执行程序时 应用程序无法正常启动

前提&#xff1a; 我的工程中包含5个子项目&#xff0c;项目1生成 camer.exe 项目2生成 dll1.dll &#xff0c;其中项目1 依赖后面的四个子项目。 但我在打包程序时&#xff0c;只运行了windeployqt F:\workspace\\bin-ne\camer.exe 将打包的程序放在纯净版本上时&#xff0…

Django开发

1.创建项目 创建项目&#xff1a; 删除内容&#xff1a; 2.创建APP 创建APP 注册APP&#xff1a; 3.设计表结构 4.在SQL中生成表 工具连接MySQL生成数据库&#xff08;在cmd中执行&#xff09;&#xff1a; create database Day1 DEFAULT CHARSET utf8 COLLATE utf8_…

融云获 51CTO 年终评选「中国 IT 行业政企数智办公优秀解决方案奖」

今日&#xff0c;51CTO 主办的“2022 年第十七届中国企业年终评选”榜单新鲜出炉&#xff0c;融云凭借百幄数智办公平台在政企信创办公领域的创新方案和独特设计&#xff0c;斩获“2022 年度中国 IT 行业政企数智办公优秀解决方案奖”。关注【融云 RongCloud】&#xff0c;了解…

B站年度产品榜 | 10项行业品类全面透析Z世代消费偏好

12月19日&#xff0c;哔哩哔哩官方发布“2022年 BILIBILI Z100”&#xff0c;这是B站官方推出的「UP主在用」年度产品榜&#xff0c;榜单包含10项产品类目87件产品&#xff0c;囊括美妆个护、数码3C、食品饮料、家居家电、交通出行、办公用品、鞋服箱包饰品、宠物用品、运动健康…

Linux线程控制

写在前面 我们今天来看线程的知识点&#xff0c;这个博客的内容很多&#xff0c;主要就是为了我们后面的网络做铺垫&#xff0c;最关键的是相比较于进程而言&#xff0c;线程是更加优秀的&#xff0c;我们现在的计算机大多采用的就是线程. 线程 我之前谈过在创建子进程的时候…

操作系统期末考试必会题库4——设备管理

1、DMA方式和中断控制方式的主要区别是什么&#xff1f; 2、面向块设备和面向流设备有何区别&#xff1f;各举一些例子 面向块的设备将信息保存在块中&#xff0c;块的大小通常是固定的&#xff0c;传送过程中一次传送一块。通常可以通过块号访问数据。磁盘和USB智能卡都是面向…

【Linux】Linux权限(一)文件权限和目录权限

Linux权限1.Linux权限的概念2.Linux的用户分类3.Linux的文件类型3.1如何看待Linux下的文件后缀4.Linux的文件权限5.Linux下切换用户指令6.Linux文件访问者的分类&#xff08;拥有者、所属组、other&#xff09;6.1root 和普通用户 与 拥有者和所属组和其他人的关系6.2 如何描述…

matlab中ginput函数的用法

仅用来记录自己学习中不会的函数 ginput函数&#xff1a;来自鼠标或光标的图形输入 一、语法 [x,y] ginput(n) [x,y] ginput [x,y,button] ginput(…) 二、说明 ginput 提高当前坐标区中的交叉线以供您标识图窗中的点&#xff0c;从而使用鼠标定位光标。图窗必须具有焦点…

【Linux】 第八部分 Linux常用基本命令

【Linux】 第八部分 Linux常用基本命令 文章目录【Linux】 第八部分 Linux常用基本命令8. Linux常用基本命令8.1 帮助命令8.2 文件目录类命令pwd 显示当前工作目录的绝对路径cd 切换目录ls 列出目录的内容mkdir 创建目录rmdir 删除目录touch 创建文件cp 复制文件或者目录rm 删除…

网络编程 异步选择模型

目录 1.概念 2.代码样例 1.概念 基本概念&#xff0c;在这一个模型中的代码使用到了vs中窗口应用程序&#xff0c;可以看这一片文章https://blog.csdn.net/weixin_62859191/article/details/128415737?spm1001.2014.3001.5501https://blog.csdn.net/weixin_62859191/article/d…

站在2023起跑线,政企数字化如何深入“核心地带”?

今天&#xff0c;各行各业都积极开展数字化变革&#xff0c;以云为底座开展数字化已成为行业共识。而更进一步观察会发现&#xff0c;大型政企作为数字化转型的先行者和主力军&#xff0c;已经从资源上云、应用上云阶段&#xff0c;率先抵达了数字化深水区&#xff0c;迈入了深…