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

news2025/1/19 2:35:53

  • 🎥 个人主页: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 关键字,告诉编译器不要优化!!!

 

 

 

 

 

 

 

 

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

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

相关文章

核心篇-OSPF技术之序(上)

文章目录 一. 实验专题1.1. 实验1&#xff1a;配置单区域OSPF1.1.1. 实验目的1.1.2. 实验拓扑1.1.3. 实验步骤&#xff08;1&#xff09;配置地址&#xff08;2&#xff09;运行OSPF 1.1.4. 实验调试&#xff08;1&#xff09;查看接口信息&#xff08;2&#xff09;查看邻居状…

飞天使-linux操作的一些技巧与知识点9-zabbix6.0 容器之纸飞机告警设置

文章目录 zabbix 告警纸飞机方式webhook 方式 zabbix 告警纸飞机方式 第一种方式参考 https://blog.csdn.net/yetugeng/article/details/99682432bash-4.4$ cat telegram.sh #!/bin/bashMSG$1TOKEN"61231432278:AAsdfsdfsdfsdHUxBwPSINc2kfOGhVik" CHAT_ID-41dsdde…

Redis——集群环境部署

一般情况下的Redis&#xff0c;我们都是在一台服务器上进行操作的&#xff0c;也就是说读、写以及备份操作都是在一台Redis服务器上进行的。随着项目访问量的增加&#xff0c;对Redis服务器的操作也更加频繁&#xff0c;虽然Redis读写速度都很快&#xff0c;但是一定程度上也会…

基于BatchNorm的模型剪枝【详解+代码】

文章目录 1、BatchNorm&#xff08;BN&#xff09;2、L1与L2正则化2.1 L1与L2的导数及其应用2.2 论文核心点 3、模型剪枝的流程 ICCV经典论文&#xff0c;通俗易懂&#xff01;论文题目&#xff1a;Learning Efficient Convolutional Networks through Network Slimming卷积后能…

GC调优工具

1、jstat 2、VisualVM GC tool插件 插件下载地址&#xff1a;https://blog.csdn.net/jushisi/article/details/109655175 3、Prometheus和Grafana监控

Xshell

更改背景颜色 多个会话同时执行命令 查看 -> 撰写 -> 撰写窗格

备战蓝桥杯---动态规划(基础3)

本专题主要介绍在求序列的经典问题上dp的应用。 我们上次用前缀和来解决&#xff0c;这次让我们用dp解决把 我们参考不下降子序列的思路&#xff0c;可以令f[i]为以i结尾的最大字段和&#xff0c;易得&#xff1a; f[i]max(a[i],a[i]f[i-1]); 下面是AC代码&#xff1a; #in…

Go 语言 for 的用法

For statements 本文简单翻译了 Go 语言中 for 的三种用法&#xff0c;可快速学习 Go 语言 for 的使用方法&#xff0c;希望本文能为你解开一些关于 for 的疑惑。详细内容可见文档 For statements。 For statements with single condition 在最简单的形式中&#xff0c;只要…

Redis -- 渐进式遍历

家&#xff0c;是心的方向。不论走多远&#xff0c;总有一盏灯为你留着。桌上的碗筷多了几双&#xff0c;笑声也多了几分温暖。家人团聚&#xff0c;是最美的风景线。时间&#xff1a;2024年 2月 8日 12:51:20 目录 前言 语法 示例 前言 试想一个场景,那就是在key非常多的…

【数据结构与算法-初学者指南】【附带力扣原题】队列

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《数据结构与算法&#xff1a;初学者入门指南》&#x1f4d8;&am…

【项目问题解决】java. net.SocketException: Connection reset

目录 【项目问题解决】java. net.SocketException: Connection reset 1.问题描述2.问题原因3.解决思路4.解决方案5.总结6.参考 文章所属专区 项目问题解决 1.问题描述 通过JMeter 压测接口&#xff0c;无并发&#xff0c;无间歇时间跑接口10000次报错&#xff0c;后续改成建个…

云卷云舒:论超级数据库、算网数据库、智算数据库

笔者大胆提出一种“超级数据库”的概念设想。 一、超级能力 就像当初提出“超级计算机”一样&#xff0c;我们是否同样可以提出“超级数据库”的概念呢&#xff1f;当然不是不可以。 二、超级计算机 我们回忆一下“超级计算机”的发展之路&#xff0c;大致经过了如下几个环…

Swift Combine 管道 从入门到精通三

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二 1. 用弹珠图描述管道 函数响应式编程的管道可能难以理解。 发布者生成和发送数据&#xff0c;操作符对该数据做出响应并有可能更改它&#xff0c;订阅者请求并接收这些数据。 这…

FPGA实现ISP用于无人车、无人机配送的方案调研

查到一个always 奥唯思公司做的用FPGA实现ISP的方案&#xff0c;采用易灵思钛金16nm的FPGA Ti60F225&#xff0c;通过MIPI CSI RX采集图像传感器的数据&#xff0c;在FPGA内部经过一系列复杂的ISP运算后&#xff0c;再通过MIPI CSI TX将图像数据发送给后端。 一套完整的ISP&a…

【前端】实现Vue组件页面跳转的多种方式

目录 前言1. 内嵌2. 跳转新页面2.1 Demo12.2 Demo22.3 Demo3 3. 拓展 前言 通过某个Button让页面多种方式跳转 1. 内嵌 想要在Vue应用中内嵌一个外部网页&#xff0c;可以使用<iframe>标签 下面是一个示例&#xff1a; <template><div><!-- 在这里嵌入…

C语言一招教你搞定所有基础函数知识(上)

1.前言 Hello&#xff0c;大家好&#xff0c;我是小陈&#xff0c;新年快乐呀&#xff01;马上就龙年了&#xff0c;今天是大年30&#xff0c;祝各位新的一年更上一层楼&#xff0c;今天小陈为大家带来了C语言的函数基础知识&#xff0c;希望大家仔细看完。 2.函数的概念 何…

数据库切片大对决:ShardingSphere与Mycat技术解析

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 数据库切片大对决&#xff1a;ShardingSphere与Mycat技术解析 前言ShardingSphere与Mycat简介工作原理对比功能特性对比 前言 在数据库的舞台上&#xff0c;有两位颇受欢迎的明星&#xff0c;它们分别…

Unity入门学习

目录 Unity环境搭建Unity引擎是什么软件下载和安装工程文件夹 Unity界面基础Scene场景和Hierarchy层级窗口Game游戏和Project工程Inspector和Console工具栏和父子关系 Unity工作原理反射机制和游戏场景预设体和资源包的导入导出 Unity脚本基础脚本基本规则生命周期函数Inspecto…

Aethir和Well-Link Tech携手革新云游戏,释放人工智能(AI)潜力

​Aethir将为Well-Link Tech的2亿用户提供先进的GPU计算能力&#xff0c;大幅提升他们的游戏体验。 新加坡&#xff0c;2024年2月7日 - 先驱性的去中心化GPU网络Aethir与实时云渲染技术领导者Well-Link Tech携手共创云游戏和元宇宙发展的新时代。 借助Well-Link Tech对领先游戏…

算法---回溯(正文)

1.什么是回溯&#xff1f; 回溯算法的定义就是和暴力枚举一样枚举所有可能并加撤回&#xff0c;也能和暴力一样去掉一些重复&#xff08;在之前就被筛出&#xff0c;但还要枚举这个&#xff0c;我们可以跳过这个了---------这个就是回溯剪枝&#xff09;。但为什么回溯不是暴力…