多线程基础详解(看到就是赚到)

  • 🎥 个人主页:Dikz12
  • 📕格言:那些在暗处执拗生长的花,终有一日会馥郁传香
  • 欢迎大家👍点赞✍评论⭐收藏

目录

 创建线程

 1.创建类继承Thread,重写run()

 2.实现Runnable,重写run()

3.继承Thread,使用匿名内部类

 4.使用lambda表达式(推荐)

线程启动 

线程中断

1.手动设置标志位

2.使用内部自带的标志位(interrupt)

线程等待 

线程状态 

线程安全

 synchronized(可重入锁) 使用方法

 死锁

关于死锁问题 

死锁能产生,一定涉及到四个必要条件 

volatile关键字(解决内存可见性问题) 


 创建线程

 1.创建类继承Thread,重写run()

class MyThread extends Thread {
    @Override
    public void run() {
        //这个就是线程的入口方法
        while (true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread(); //向上转型
        t.start();
        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 2.实现Runnable,重写run()

class  MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

3.继承Thread,使用匿名内部类

  /* //实现了Runnable,匿名内部类的写法
      Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });*/
        Thread t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

 4.使用lambda表达式(推荐)

 public static void main(String[] args) {
        //lambda 表达式 本质上是一个匿名函数,用来实现回调函数
        Thread t = new Thread(() ->{
            while (true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

不单单只有上诉这几种,还有其它方式就不在演示了! 

线程启动 

线程启动是通过start(). 而不是run().

run():    只是单纯的描述了当前线程要执行的内容.

start() : 才是真的会调用 系统api,在系统内核上创建线程.

线程中断

1.手动设置标志位

    private static boolean isQuit = false; //成员变量
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!isQuit) {
                System.out.println("线程开始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程结束");
        });
        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isQuit = true;
        System.out.println("设置 isQuit 为 true");
    }

 要注意的是:这里用的是lambda表达式的写法,会发生变量捕获,自动捕获上层域涉及的局部变量.

是有前提限制的,就是只能捕获一个要保证是实际上的final 变量.

2.使用内部自带的标志位(interrupt)

 public static void main(String[] args) {
        Thread t = new Thread(() -> {
           while (!Thread.currentThread().isInterrupted()) {
               System.out.println("线程工作中");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
                   //1.什么都不加, 假装没听见; 继续执行
                   //2.加上break, 表示线程立即结束
                   //break;
                   //3. 可以做一些其它工作,(代码放到这里)执行完之后,在结束
                   break;
               }
           }
            System.out.println("线程结束");
        });
        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }

线程等待 

 让一个线程,等待另一个线程执行结束,然后在执行.   本质上就可以理解为控制线程的结束顺序.

join() -> 个哪线程调用,哪个线程就阻塞等待.

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t1 结束");
        });
        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2 结束");
        });
        t1.start();
        t2.start();
        System.out.println("主线程结束!");
    }

 

线程状态 

 在Java中,又给线程赋予了一些其它的状态.比如:

NEW: Thread对象已经创建好了,到时start()还没调用.

TERMINATED : Thread对象还在,内核中的线程已经被销毁了.

RUNNABLE: (就绪状态) 线程已经在cpu上执行了/正在排队等待cpu执行.

WAITING(阻塞): 由于wait()引起的阻塞.

TIMED_WAITING: 由于sleep() 引起的阻塞. 

BLOCKED: 由于锁竞争导致的阻塞.

线程状态在调试的时候,可以使用jdk文件下bin目录中的查看线程状态. 

线程安全

 想要解决线程安全问题,就要先了解产生线程不安全的原因.

1.在操作系统中,线程的调度顺序是随机的.(这是由系统内核决定的,除非换个系统)

2.两个线程,对一个变量进行修改

3.修改操作不是原子性的

4.内存可见性问题

5.指令重排序问题

比如:  针对一个变量进行修改.

 private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(() -> {
           for (int i = 0; i < 50000; i++) {
               //加锁
               synchronized (locker1) {
                   count++;
               }
           }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                synchronized(locker1) {
                    count++;
                }
            }
        });
        t1.start();
        t2.start();
        //如果没有这俩 join, 肯定不行的. 线程还没自增完, 就开始打印了. 
        t1.join();
        t2.join();
        //预期结果应该是10W
        System.out.println(count);
    }

 这里引入锁synchronized(可重入锁),作用就是把count 这个变量,成为 原子的, 也就是降低了并发程度.

 synchronized(可重入锁) 使用方法

1.搭配代码块使用

 

2.搭配实例方法或者静态方法

    public int count;
    public void increase () {
        synchronized (this) {
            count++;
        }
    }
    //简化版
    synchronized  public void increase2() {
        count++;
    }
    //静态方法
    public static  void incresae3() {
        synchronized (Fun.class) {

        }
    }
    synchronized public static void increase4() {

    }

 死锁

关于死锁问题 

1.一个线程,针对 同一把锁,连续加锁,如果不是可重入锁,就会发生死锁.(Java中的synchronized是可重入的;C++的std::mutex 就是不可重入锁).

2.两个线程,两把锁.

   线程t1,得到一把锁A 后,又尝试获取锁B;   线程t2 ,得到一把锁B后,有尝试获取锁A.

    public static Object locker1 = new Object();
    public static Object locker2 = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
           synchronized (locker1) {
               // 加上sleep 为了t1 和 t2 线程都能获得一把锁
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               synchronized (locker2) {
                   System.out.println("t1 加锁成功!");
               }
           }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker1) {
                    System.out.println("t1 加锁成功!");
                }
            }
        });
        t1.start();
        t2.start();
    }

 通过调试可以看到,这两个线程进入了BLOCKED的状态(死锁)

这种情况是可以避免的,调整代码结构,上述代码两个synchronized 是嵌套关系,不是并列关系.

3.N 个线程,M把锁.

    典型的例子: 操作系统中的 科学家就餐问题.(就不在详细讨论了) 

死锁能产生,一定涉及到四个必要条件 

1. 互斥使用(锁的基本特性): 一个线程得到一把锁之后,另一个线程也想得到这把锁,就要阻塞等待.

2.不可抢占(锁的基本特性): 一把锁已经被一个线程得到后,另一个线程只能等该线程主动释放,不能强行抢占.

3.请求保持 : 一个线程想获取多把锁(例子:死锁问题的第二个).

4.循环等待/ 环路等待: 线程之间的等待关系成环了. (例子:科学家就餐问题)

所以,解决死锁问题,只要破坏上述四个条件中的其中一个就可以.

1和2,是锁的基本特性,是破坏不了的,也就破坏这两个中的其中一个.

破坏3 : 只需要调整代码结结构,避免出现"嵌套" 逻辑.

破坏4: 约定加锁的顺序,就可以避免循环等待.

volatile关键字(解决内存可见性问题) 

    private static int isQuit = 0;
    public static void main(String[] args) {
        Thread t1 = new Thread(() ->  {
           while(isQuit == 0) {
               //循环体
           }
            System.out.println("t1 退出");
        });
        t1.start();
        Thread t2 = new Thread(() ->  {
            System.out.println("请输入isQuit的值:");
            Scanner scanner = new Scanner(System.in);
             isQuit = scanner.nextInt();
        });
        t2.start();
    }

上述代码 运行效果:

期望的结果是,输入1,线程结束. 而这里并没有结束.

 就需要站在cpu的分析下,整个数据的过程:

1.load 读取内存的isQuit值放到寄存器里

2.通过cmp指令比较寄存器得值是否等于0,决定是否要继续执行

读取内存的速度就已经是非常快的了,而读取寄存器的速度是 读取内存速度的 几千倍 几万倍.

所以,Java的编译器就自主做了一个大胆的决定,编译优化,只有第一次循环的时候,才读了内存,后面都是读取寄存器.

解决方案就是通过 volatile 关键字,告诉编译器不要优化!!!

 

 

 

 

 

 

 

 

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

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

相关文章

版本控制工具——Git

版本控制工具——Git 前言一、版本库二、git的工作区域和文件状态三、添加和提交文件四、回退版本&#xff1a;git reset --模式 版本号五、查看差异&#xff1a;git diff六、从版本库中删除文件七、.gitignore&#xff1a;git中的特殊文件八、Git、GitHub跟Sourcetree的关系九…

基于 Python 的漏洞扫描系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【Python】使用 requirements.txt 与 pytorch 相关配置

【Python】使用 requirements.txt 与 pytorch 相关配置 前言一、pip1、导出结果含有路径2、导出不带路径的 二、Conda1、导出requirements.txt2、导出yml 文件 三、第三方包&#xff1a;pipreqs&#xff08;推荐&#xff09;1、创建并激活conda环境2、安装requirements文件的pi…

金融信贷风控评分卡模型

评分卡模型概念 评分模型是根据借款人的历史数据&#xff0c;选取不同维度的数据类型&#xff0c;通过计算而得出的对借款人信用情况打分的模型。不同等级的信用分数代表了借款人信用情况的好坏&#xff0c;以此来分析借款人按时还款的可能性。 评分卡模型分类 A卡&#xff…

揭开Markdown的秘籍:标题|文字样式|列表

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;Markdown指南、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️Markdown 标题二. ⛳️Markdown 文字样式2.1 &#x1f514;斜体2.2 &…

Netty连接通道中的Channel参数模型

ChannelOption(Channel中的连接参数) ChannelOption.SOBACKLOG ChannelOption.SO_BACKLOG对应的是tcp/ip协议listen函数中的backlog参数&#xff0c;服务端处理客户端连接请求是顺序处理的&#xff0c;所以同一时间只能处理一个客户端连接&#xff0c;多个客户端来的时候&…

代理与Reflect反射

属性描述符 Proprety Descriptor 属性描述符 用于描述一个属性的相关信息 1.Object.getOwnPropertyDescriptor(对象&#xff0c;属性名) 可以得到一个对象的 某个属性的属性描述符 Object.getOwnPropertyDescriptors(对象) 可以得到某个对象的所有属性描述符 如果需要为某个…

node.js基础-02

Author nodes&#xff1a;&#xff08;题记&#xff09; Hypertest Transfer protocol is very important to programming personnel。it doesnt matter if youre a front-end engineer or a back-end engineer.So,lets study it together. http协议对于编程工程师很重要&am…

基于华为云欧拉操作系统(HCE OS)单节点容器化部署(Prometheus、node-exporter、Grafana)应用性能监控平台

写在前面 博文内容为 华为云欧拉操作系统入门级开发者认证(HCCDA – Huawei Cloud EulerOS)实验笔记整理认证地址&#xff1a;https://edu.huaweicloud.com/certificationindex/developer/9bf91efb086a448ab4331a2f53a4d3a1内容涉及&#xff0c;HCE OS 容器化部署(Prometheus、…

【C++修行之道】(引用、函数提高)

目录 一、引用 1.1引用的基本使用 1.2 引用注意事项 1.3 引用做函数参数 1.4 引用做函数返回值 1.5 引用的本质 1.6 常量引用 1.7引用和指针的区别 二、函数提高 2.1 函数默认参数 2.2函数占位参数 2.3 函数重载 2.4函数重载注意事项 一、引用 1.1引用的基本使用 …

【canvas】获取鼠标点击位置坐标的颜色信息

在项目当中&#xff0c;要实现某业务需求例如PS魔棒功能时&#xff0c;则需要获取点击坐标的颜色信息。 功能不复杂&#xff0c;代码也很少&#xff0c;一看便知~~ 核心API为getImageData&#xff0c;传入4个参数&#xff0c;前2个为点击坐标xy&#xff0c;后2个都传1&#xf…

那些 C语言指针 你不知道的小秘密 (3)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能…

vscode连接ssh报错

关于vscode更新版本至1.86后&#xff0c;导致无法连接服务器问题的记录 原因&#xff1a;vscode1.86更新了对glibc的要求&#xff0c;需要最低2.28版本&#xff0c;导致各种旧版本的linux发行版&#xff08;比如最常见的centos 7&#xff09;都无法用remote-ssh来连接了&#…

Linux探秘之旅:透彻理解路径、命令与系统概念

目录 如何远程连接 远程登录简明指南 linux区别 1.严格区分大小写 2.linux的命令返回结果判断 3.如何查看网络信息 4.关于后缀名&#xff08;Linux不关心文件后缀&#xff09; 4.1 需要记忆的后缀 5.echo命令 6.linux一切皆文件 6.1比如磁盘的文件 6.2可执行文件 …

nginx + DNS域名解析

配置链接: Nginx 安装配置 | 菜鸟教程 安装完nginx后&#xff0c;访问&#xff1a; cd /usr/local/nginx/sbin/ 然后使用./nginx可使用nginx。 访问:http://服务器的ip地址后出现 因为访问IP地址很繁琐&#xff0c;需要记忆ip的数字地址&#xff0c;因此需要给它一个通俗的…

如何在Sprint中管理UI测试?

作为iOS团队&#xff0c;我们编写3种类型的UI测试。如果你问这些是什么&#xff1b;快照、冒烟和回归测试。那么这些测试到底是什么&#xff1f;让我们稍微谈谈这些。 快照测试快照测试是检查UI中的某些内容是否损坏的测试。 首先&#xff0c;它将所需的视图图像保存在某处&am…

Lombok 高级说明

优质博文&#xff1a;IT-BLOG-CN 一、痛点 【1】代码臃肿&#xff1a;POJO中的getter/setter/equals/hashcode/toString等&#xff1b; 【2】样板式代码&#xff1a;I/O流的关闭操作等&#xff1b; Lombok是一个可以通过注解简化Java代码开发的工具&#xff0c;能够在我们编…

string容器

1. string基本概念 1.1 本质&#xff1a; string是C风格的字符串&#xff0c;而string本质上是一个类 string和char * 区别&#xff1a; char * 是一个指针 string是一个类&#xff0c;类内部封装了char*&#xff0c;管理这个字符串&#xff0c;是一个char*型的容器。 1.2 特点…

书生·浦语大模型全链路开源体系

参考&#xff1a;https://www.bilibili.com/video/BV1Rc411b7ns/?spm_id_from333.788&vd_source3bbd0d74033e31cbca9ee35e111ed3d1 背景&#xff1a; 人工智能的发展从针对特定任务&#xff0c;用一个模型解决一个问题过渡到一个模型来应对多模态、多任务&#xff0c;大模…

K210如何下载程序

一、打开资料包里提供的K-Flash程序烧录软件 二、选择串口 三、选择波特率 四、选择In-Chip&#xff0c;烧录到Flash芯片里面&#xff0c;重新上电还会运行程序 五、如果选择In - Memory&#xff0c;这次可以运行&#xff0c;但下次重新上电就不会保持这次的程序了。 六、选择固…