线程的学习

news2025/7/18 17:04:47

v# 1. 线程基本概念

1.1进程

  1. 进程是指运行中的程序,比如启动了QQ,就相当于启动了一个进程,操作系统会为该进程分配空间;当我们使用迅雷,就相当于又启动了一个进程,操作系统将为迅雷分配新的内存空间;
  2. 进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程,有它自身的产生、存在、消亡的过程;
  3. Run代表启动了一个进程,会马上进入main方法,这时进程就开启了一个线程,即主线程;
    在这里插入图片描述
    这时再启用start()方法就会在主线程里面再启动一个子线程:Thread-0;
    子线程在执行的时候,主线程不会阻塞,会继续执行;
    在这里插入图片描述

1.2线程

  1. 线程由进程创建的,是进程的一个实体;
  2. 一个进程可以拥有多个线程;
  3. 单线程:在同一个时刻,只允许执行一个线程;
  4. 多线程:同一个时刻,可以执行多个线程。比如,一个qq进程,可以同时打开多个聊天窗口;一个迅雷进程,可以同时下载多个文件;

1.3 并发

(1)单核CPU实现多任务;(2)同一个CPU,在某个时刻,多个任务交替执行;CPU速度很快,造成“QQ”和“迅雷”貌似同时执行的错觉;
在这里插入图片描述

1.4 并行

(1)在同一个时刻多个任务同时执行,多核CPU可以实现并行;

在这里插入图片描述

获取当前电脑Cpu数量
在这里插入图片描述

3. 线程基本使用

在这里插入图片描述

3.1 继承Thread类

  1. 当一个类继承了Thread类,该类就可以当作线程使用;
  2. Thread类实现了Runnable接口的run方法
    在这里插入图片描述
  3. 重写run方法,实现自己的业务逻辑
    在这里插入图片描述
  4. 调用start()方法会调用run()方法
    在这里插入图片描述

3.1.1 子线程

  1. Run代表启动了一个进程,会马上进入main方法,这时进程就开启了一个线程,即主线程;
  2. 这时再启用start()方法就会在主线程里面再启动一个子线程:Thread-0;
  3. 子线程在执行的时候,主线程不会阻塞;
  4. 子线程、主线程会交替执行;
    在这里插入图片描述
  5. 主线程的名称是main,子线程的名称从Thread-0开始;

3.1.1.1 JConsole监控

  • 验证:使用JConsole监控线程执行情况:
    在这里插入图片描述
    在这里插入图片描述

3.1.1.2 直接run()方法

  1. 此时的run()方法只是个普通方法,直接调用并不会开启子线程,此时Thread.currentThread().getName()是main
  2. 直接调用run方法不会开启子线程,默认在主线程内执行;
    在这里插入图片描述
  3. 会把run()方法执行完毕,才向下执行,串行化地执行,程序会阻塞;
    在这里插入图片描述

3.1.1.2 调用start()方法

  • 真正实现多线程的效果,是start0()方法
  1. cat调用start()方法,进入到Thread类的start()方法,start()方法调用start0()方法;
    在这里插入图片描述
  2. 进入到Thread类中的start()方法;start0()是本地方法,JVM调用,用C/C++编写;
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.2 实现Runable接口

  1. 假如一个A类继承了B类,那么它就不能再继承Thread类,这个时候让A类实现Runable接口,同样能实现线程;
  2. Runable接口里没有start()方法,只有个run方法;
    在这里插入图片描述
    解决方案:dog对象赋给了Thread类中的target,thread.start()方法调用start0()方法,start0()方法调用本类的run方法,再调用target的run方法;在这里插入图片描述

3.2.1 线程代理模式

用代码模拟实现Runnable接口实现线程的机制;

  1. 把Proxy类当作Thread类
public class Thread02 {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();//实现了Runnable接口
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

实现步骤: 先创建一个Proxy类,继承Runnable

class ThreadProxy implements Runnable {//把Proxy类当作 ThreadProxy 线程代理

    private Runnable target = null;
    @Override
    public void run() {
        if (target != null) {
            target.run();//动态绑定(运行类型Tiger)
        }
    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    public void start() {
        start0();//真正实现多线程的方法
    }

    public void start0() {
        run();
    }
}
  1. 再创建一个普通类,实现Runnable接口;
class Tiger extends Animal implements Runnable{

    @Override
    public void run() {
        System.out.println("老虎发威~~");
    }
}

3.2.2 多线程机制

两个子线程会交替执行
在这里插入图片描述
在这里,主线程看不到
在这里插入图片描述
在这里插入图片描述

3.3 继承Thread Vs 实现Runnable接口

  1. 本质没有区别,因为Thread类本身就实现了Runnable接口的run()方法
  2. 但是实现Runnable接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制;(建议使用Runnable接口)
  3. 继承Thread实现线程无法做到资源共享,并且受到单继承的限制;
    在这里插入图片描述

3.3.1 售票系统超卖问题

sleep()要在–tickets前面才能超卖

public class SellTicket {
    public static void main(String[] args) {
        /*SellTicket1 sellTicket1 = new SellTicket1();
        SellTicket1 sellTicket2 = new SellTicket1();
        SellTicket1 sellTicket3 = new SellTicket1();
        sellTicket1.start();
        sellTicket2.start();
        sellTicket3.start();*/
        SellTicket1 sellTicket1 = new SellTicket1();
        Thread thread1 = new Thread(sellTicket1);
        Thread thread2 = new Thread(sellTicket1);
        Thread thread3 = new Thread(sellTicket1);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class SellTicket1 extends Thread {
    //private static int tickets = 100;
    private int tickets = 100;
    int count = 0;
    @Override
    public void run() {
        while (true) {
            if (tickets <= 0) {
                break;
            }
            System.out.println("窗台:" + Thread.currentThread().getName() +
                    "出售了" + (++count) + "张票,剩余票数:" + (--tickets));
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3.3.2 线程终止

  1. 当线程完成任务后,会自动退出;
  2. 可以通过变量来控制run方法退出线程,即通知方式

3.3.2.1 通知线程退出

启动一个线程t,要求在main线程中去停止线程t
解决方案:在主线程中通过Setter方法将loop变量设置为false
在这里插入图片描述
在这里插入图片描述

3.3.3 线程基本方法

方法名作用
setName设置线程名称
getName获取线程名称
setPriority设置线程优先级
getPriority获取线程优先级
interrupt中断线程

在这里插入图片描述

3.3.3 线程中断

  1. interrupt中断线程,但并没有真正的结束线程,所以一般用于中断正在休眠的线程;(中断休眠,唤醒线程)
  2. sleep(),线程的静态方法,使当前线程休眠;
public class ThreadMethod_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("赵志伟");//设置线程名称
        t.setPriority(Thread.MIN_PRIORITY);//设置线程优先级
        t.start();//启动了子线程
        System.out.println(t.getName());//获取线程名称

        //主线程打印5个hi,然后就中断子线程的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi~~" + i);
        }
        System.out.println(t.getName() + "线程的优先级" + t.getPriority());
        t.interrupt();//执行到interrupt()方法时,中断线程的休眠
    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                //获取当前线程的名称
                System.out.println(Thread.currentThread().getName() + " 吃包子~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + " 正在休眠中~~");
                Thread.sleep(20 * 1000);
            } catch (InterruptedException e) {
                //当该线程执行到一个interrupt 方法时,就会catch 一个异常,可以加入自己的业务代码
                // InterruptedException捕获了一个中断异常
                System.out.println(Thread.currentThread().getName() + "被中断了");
            }
        }
    }
}

3.3.4 线程礼让

yield:线程的礼让,让出cpu,让其它线程执行,但礼让的时间不确定,所以不一定礼让成功;

3.3.5 线程插队

join:线程插队,一旦插入成功,则先执行完插入的线程的所有的任务;

public class Thread03 {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        Thread thread = new Thread(t);
        thread.start();
        for (int i = 1; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "(小弟)吃了" + i + "个");
            if (i == 10) {
                System.out.println("main线程(小弟)让子线程(大哥)先吃");
//                thread.join();//子线程(大哥)插队,大哥吃完小弟再吃
                Thread.yield();//main线程(小弟)礼让,不一定成功...
            }
        }
    }
}

class T implements Runnable {
    private int count = 0;
    private boolean loop = true;
    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大)吃了" + (++count) + "个");
            if (count == 20) {//吃到20个结束
                System.out.println("子线程(大哥)吃完了,main线程(小弟)继续吃");
                break;
            }
        }
    }
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

3.3.5.1 线程插队练习

public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        Thread thread = new Thread(t1);
        for (int i = 1; i <= 10; i++) {
            Thread.sleep(1000);
            System.out.println("main线程:hi " + i);
            if (i == 5) {
                thread.start();
                thread.join();//子线程启动后直接插队
            }
        }
    }
}

class T1 implements Runnable {
    private int count;
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程: hello " + (++count));
            if (count == 10) {
                break;
            }
        }
    }
}

3.3.6 守护线程

用户线程:也叫工作线程、当线程的任务执行完或通知方式结束;
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束;
常见的守护线程:垃圾回收机制;

主线程结束,子线程并不会结束;如果我们希望主线程结束时,子线程也结束,可以将子线程设置为守护线程;

public class ThreadMehod03 {
    public static void main(String[] args) {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        Thread thread = new Thread(myDaemonThread);
        thread.setDaemon(true);//将子线程设置为守护线程
        thread.start();
        for (int i = 1; i <= 10; i++) {
            System.out.println("主线程" + Thread.currentThread().getName() + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class MyDaemonThread implements Runnable {
    @Override
    public void run() {
        for (; ; ) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程" + Thread.currentThread().getName());
        }
    }
}

3.3.7 线程的声明周期

  1. JDK中用Thread.State枚举表示了线程的七种(六种)状态
    在这里插入图片描述
public class ThreadState_ {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        Thread thread = new Thread(t2);
        System.out.println(thread.getName() + "状态 " + thread.getState());//NEW
        thread.start();
        //子线程和主线程同时执行
        /*for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);//主线程休眠、主线程输出
            System.out.println(thread.getName() + "状态 " + thread.getState());//RUNNABLE
        }*/
        while (Thread.State.TERMINATED != thread.getState()) {
            Thread.sleep(1000);
            System.out.println(thread.getName() + "状态 " + thread.getState());//RUNNABLE、TIMED_WAITING
        }
        System.out.println(thread.getName() + "状态 " + thread.getState());//TERMINATED
    }
}

class T2 implements Runnable {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);//子线程输出
                try {
                    Thread.sleep(1000);//子线程休眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

3.3.8 线程同步机制

  1. 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任意一个时刻,最多有一个线程访问,以保证数据的完整性;
  2. 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其它线程都不可以对这个内存地址进行操作,知道该线程完成操作后,其它线程才能对该内存地址进行操作;

3.3.9 互斥锁

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性;

  2. 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象;

  3. 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问;

  4. 同步的局限性:会导致程序的执行效率降低;

  5. 同步方法(非静态的)的锁可以是this,也可以是其它对象(要求是同一个对象)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  6. 同步方法(静态的)的锁为当前类本身;

  7. 方法上加锁、代码块上加锁;

  8. 同步方法如果没有使用static修饰,默认锁对象为this;

  9. 如果方法使用static修饰,默认锁对象:当前类.class; 如果想在静态方法中,实现一个同步代码块,写类本身;
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.4.0 死锁

public class DeadLock_ {
    public static void main(String[] args) {
        DeadLockDemo deadLockDemo = new DeadLockDemo(true);
        deadLockDemo.setName("A线程");
        deadLockDemo.start();
        DeadLockDemo deadLockDemo1 = new DeadLockDemo(false);
        deadLockDemo1.setName("B线程");
        deadLockDemo1.start();
    }
}

class DeadLockDemo extends Thread {
    static Object o1 = new Object();//保证多线程,共享一个对象,使用static
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {//构造器
        this.flag = flag;
    }

    @Override
    public void run() {
        //(1)如果flag为true,线程A 就会先得到/持有 o1对象锁,然后尝试去获取o2对象锁
        //(2)如果flag为true,线程A 就会先得到/持有 o1对象锁,然后尝试去获取o2对象锁
        if (flag) {
            synchronized (o1) {
                System.out.println(Thread.currentThread().getName() + "进入1");
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + "进入2");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + "进入3");
                synchronized (o1) {
                    System.out.println(Thread.currentThread().getName() + "进入4");
                }
            }
        }
    }
}

3.4.1 释放锁

wait():当前线程暂停,并释放锁;
sleep()、yield():不会释放锁;

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

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

相关文章

坦克大战①

1. java绘图技术 JFrame&#xff1a;画板 Jpanel&#xff1a;画板 Graphics&#xff1a;画笔 初始化画板&#xff0c;定义画框的大小&#xff0c;设置可视化&#xff1b; 1.1 画坦克 初始化我方坦克、敌方坦克 绘图&#xff1a;(1)更改背景颜色&#xff1b;(2)绘制敌方坦克…

【Docker】常用命令

背景 当下&#xff0c;docker技术已成为开发者常用的技术栈。不管是开发过程中需要应对的各种复杂多变的开发环境的搭建&#xff0c;还是生产部署环节需要的自动化运维&#xff0c;都离不开docker&#xff0c;本文简单介绍相关命令的含义&#xff0c;用作平时查询使用。 1. doc…

【计算机毕业设计】38.网上轰趴预订系统

一、系统截图&#xff08;需要演示视频可以私聊&#xff09; 摘要 在网上轰趴发展的整个过程中&#xff0c;网上轰趴预定担负着最重要的角色。为满足如今日益复杂的管理需求&#xff0c;各类网上轰趴程序也在不断改进。本课题所设计的网上轰趴预定系统&#xff0c;使用SSM框架…

一文看懂Transformer(详解)

文章目录Transformer前言网络结构图&#xff1a;EncoderInput EmbeddingPositional Encoderself-attentionPadding maskAdd & NormFeed ForwardDecoderinputmasked Multi-Head Attentiontest时的Decoder预测Transformer 前言 Transformer最初是用于nlp领域的翻译任务。 …

大屏图表,ECharts 从“熟练”到入门

&#x1f4d6;阅读本文&#xff0c;你将 了解 配置驱动 的思想理解 Echarts 基本概念了解 graphic 和 动画基本玩法。了解 Echarts 基底组件的封装的思路 一、不是标题党&#xff01;Echarts&#xff0c;简历上人均 “熟练”&#xff1f; 公司最近在招外包&#xff0c;而因为…

基于ASP学生资助管理系统的设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做ASP程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问题…

用Python蹭别人家图片接口,做一个【免费图床】吧

打开本文&#xff0c;相信你确实需要一个免费且稳定的图床&#xff0c;这篇博客就让你实现。 文章目录⛳️ 谁家的图床⛳️ 实战编码⛳️ 第一轮编码⛳️ 第二轮编码⛳️ 第三轮编码⛳️ 第四轮编码⛳️ 谁家的图床 这次咱们用新浪微博来实现【免费图床应用】&#xff0c;通过…

栈浅谈(上)

目录 栈的定义 栈的实现 初始化 判断栈是否为空 入栈操作 获取栈顶元素 出栈操作 遍历栈 销毁栈 完整代码演示 栈—STL 基本操作 例题 参考代码 栈的定义 说到栈&#xff0c;一些不会计算机语言的“小白”&#xff08;我就是&#xff09;就会想到栈道之类的词语…

基于JavaWeb的婚恋交友网站设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

如何平衡新老策略的好与坏,一道常见风控送命题解答

作为一个风控策略从业者&#xff0c;在做风险管理的过程中一直在做的就是&#xff0c;不断的挖掘有效的变量特征来填充风控决策体系&#xff0c;使决策体系的功效变大变强&#xff0c;最终形成一套可变的稳定风险护盾。我们常见的一个场景比如如何筛选一些新策略来挑战老策略&a…

【C++中预处理语句 include、define、if】

1.预处理阶段 预处理阶段&#xff0c;在实际发生编译之前就根据对应的预处理语句进行操作&#xff0c;等到预处理阶段完成之后才进行编译阶段 。 2.预处理语句 预处理语句主要有include、define、if 和 program。利用 # 进行标记 2.1 include语句 include语句就是将所包含的…

if-else练习

if单分支 输入两个数&#xff0c;分别放入x和y中&#xff0c;若两数不相等&#xff0c;则输出其中的大数&#xff0c;若两数相等&#xff0c;则输出字符串“xy&#xff1a;”并输出其值 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() {int a 0;int …

智慧油田解决方案-最新全套文件

智慧油田解决方案-最新全套文件一、建设背景1、智慧油田的概念及意义2、智慧油田的建设目标二、建设思路三、建设方案四、获取 - 智慧油田全套最新解决方案合集一、建设背景 1、智慧油田的概念及意义 石油产量、采收率、安全生产等都与石油工业未来息息相关&#xff0c;随着石…

十八、CANdelaStudio深入-Data Types

本专栏将由浅入深的展开诊断实际开发与测试的数据库编辑,包含大量实际开发过程中的步骤、使用技巧与少量对Autosar标准的解读。希望能对大家有所帮助,与大家共同成长,早日成为一名车载诊断、通信全栈工程师。 本文介绍CANdelaStudio的Data Types(数据类型),欢迎各位朋友订…

旧系统改造

背景 很多时候&#xff0c;我们在项目前期会优先确保项目业务的落地&#xff0c;在短时间内进行项目冲刺&#xff0c;最后完成项目上线。这样做让短时间内的目标达貌似达成了&#xff0c;却给系统留下了很大的隐患。 在项目的冲刺过程中&#xff0c;我们的精力大部分花在了业…

动态规划-不同路径

一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; 示例 …

单源广度优先搜索 (leetcode经典例题 C++实现)

文章目录01矩阵地图分析腐烂的橘子深度优先搜索与广度优先搜索前情回顾&#xff1a; 深度搜索dfs与广度搜索bfs算法总结&#xff08;c 例题&#xff09; 本节是广度优先搜索的进阶&#xff1a; 01矩阵 传送门&#xff1a; https://leetcode.cn/problems/01-matrix/?envType…

JavaWeb----Servlet技术

JavaEE简介 什么是JavaEE JavaEE&#xff08;Java Enterprise Edition&#xff09;&#xff0c;Java企业版&#xff0c;是一个用于企业 级web开发平台,它是一组Specification。最早由Sun公司定制并发 布&#xff0c;后由Oracle负责维护。在JavaEE平台规范了在开发企业级web 应…

【操作系统】死锁(详细)

文章目录一、死锁的概念二、死锁的产生因素三、死锁的必要条件1、互斥条件2、占有和等待条件&#xff08;部分分配条件&#xff09;3、不剥夺条件4、循环等待条件&#xff08;环路条件&#xff09;四、死锁防止1、破坏互斥条件2、破坏占有和等待条件3、破坏不剥夺条件4、破坏循…

Ceph文件系统

目录 一、环境准备 二、什么是文件系统 三、ceph块存储与文件系统区别 四、创建ceph文件系统 1、启动MDS服务 2、创建存储池 3、创建Ceph文件系统 4、客户端挂载 一、环境准备 Ceph集群搭建参照&#xff1a;Ceph集群部署_桂安俊kylinOS的博客-CSDN博客 以下Ceph存储实…