当前位置:

首页 > 编程开发 > Java多线程Thread类如何使用

Java多线程Thread类如何使用

Thread类的基本用法1.创建子类,继承自Thread并且重写run方法:classMyThreadextendsThread{@Overridepublicvoidrun(){System.out.println("hellothread");}}publicclassDemo1{publicstaticvoidmain(String[]args){//最基本的创建线程的办法.Threadt=newMyThread();//调用了start方法才是真正的在系统中创建了线程,执行run方法t.start

    Thread类的基本用法

    1.创建子类,继承自Thread并且重写run方法:

    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("hello thread");
        }
    }
    public class Demo1 {
        public static void main(String[] args) {
            // 最基本的创建线程的办法.
            Thread t = new MyThread();
            //调用了start方法才是真正的在系统中创建了线程,执行run方法
            t.start();
        }
    }

    2.创建一个类,实现Runnable接口再创建Runnable是实例传给Thread

    class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("hello");
        }
    }
    public class Demo3 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable());
            t.start();
        }
    }

    3.匿名内部类

    创建了一个匿名内部类,继承自Thread类,同时重写run方法,再new出匿名内部类的实例

    public class Demo4 {
        public static void main(String[] args) {
            Thread t = new Thread(){
                @Override
                public void run() {
                    System.out.println("hello");
                }
            };
            t.start();
        }
    }

    new的Runnable,针对这个创建的匿名内部类,同时new出的Runnable实例传给Thread的构造方法

    public class Demo5 {
        public static void main(String[] args) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            });
            t.start();
        }
    }

    4.lambda表达式 lambda代替Runnable

    public class Demo6 {
        public static void main(String[] args) {
            Thread t = new Thread(() ->{
                System.out.println("hello");
            });
            t.start();
        }
    }

    线程指标

    1.isDaemon();
    是否后台线程 后台线程不影响进程退出,不是后台线程会影响进程退出

    2.isAlive();
    是否存活 在调用start前系统中是没有对应线程的,run方法执行完后线程就销毁了,t对象可能还存在

    3.isinterrupted();
    是否被中断

    run和start的区别

    run单纯的只是一个普通方法描述了任务的内容 start则是一个特殊的方法,内部会在系统中创建线程

    中断线程

    线程停下来的关键是要让对应run方法执行完,对于main线程来说main方法执行完了才会终止

    1.手动设置标志位
    在线程中控制这个标志位就能影响到这个线程结束,但是此处多个线程共用一片虚拟空间,因此main线程修改的isQuit和t线程判断的isQuit是同一个值

    public class Demo10 {
        // 通过这个变量来控制线程是否结束.
        private static boolean isQuit = false;
        public static void main(String[] args) {
            Thread t = new Thread(() -> {
                while (!isQuit) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
    
            // 就可以在 main 线程中通过修改 isQuit 的值, 来影响到线程是否退出
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // main 线程在 5s 之后, 修改 isQuit 的状态.
            isQuit = true;
        }
    }

    2.使用Thread中内置的一个标志位来判定 Thread.interruted()这是一个静态方法 Thread.currentThread().isInterrupted()这是一个实例方法,其中currentThread能够获取到当前线程的实例

    public class Demo7 {
        public static void main(String[] args)  {
            Thread t = new Thread(() -> {
                while(!Thread.currentThread().isInterrupted()){
                    System.out.println("hello");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        // 当触发异常之后, 立即就退出循环~
                        System.out.println("这是收尾工作");
                        break;
                    }
                }
            });
            t.start();
            try{
                Thread.sleep(5000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            // 在主线程中, 调用 interrupt 方法, 来中断这个线程.
            // t.interrupt 的意思就是让 t 线程被中断!!
            t.interrupt();
        }
    }

    需要注意的是调用这个方法t.interrupt()可能会产生两种情况:
    1)如果t线程处在就绪就设置线程的标志位为true
    2)如果t线程处在阻塞状态(sleep),就会触发一个InterruptExeception

    线程等待

    多个线程之间调度顺序是不确定的,有时候我们需要控制线程之间的顺序,线程等待就是一种控制线程执行顺序的手段,此处的线程等待只要是控制线程结束的先后顺序。
    哪个线程中的join,哪个线程就会阻塞等待直到对应的线程执行完毕为止。

    t.join();
    调用这个方法的线程是main线程,针对t这个对象调用的此时就是让main等待t。
    代码执行到join这一行就停下了,让t先结束然后main继续。

    t.join(10000);
    join提供了另一个版本为带一个参数的,参数为等待时间10s之后join直接返回不再等待

    Thread.currentThread()

    能够获取当前线程的应用,哪个线程调用的currentThread就获取到哪个线程的实例 对比this如下:
    对于这个代码来说,通过继承Thread的方法来创建线程。此时run方法中直接通过this拿到的就是当前Thread的实例

    public class Demo4 {
        public static void main(String[] args) {
            Thread t = new Thread(){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(this.getName());
                }
            };
            t.start();
        }
    }

    然而此处this不是指向Thread类型,而是指向Runnable,Runnable只是一个单纯的任务没有name属性,要想拿到线程名字只能通过Thread.currentThread()

    public class Demo5 {
        public static void main(String[] args) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    //err
                    //System.out.println(this.getName());
                    //right
                    System.out.println(Thread.currentThread().getName());
                }
            });
            t.start();
        }
    }

    进程状态

    针对系统层面:

    • 就绪

    • 阻塞

    java中Thread类进一步细化:

    • NEW:把Thread对象创建好了但是还没有调用start

    • TERMINATED:操作系统中的线程已执行完毕销毁,但是Thread对象还在获取到的状态

    • RUNNABLE:就绪状态,处在该状态的线程就是在就绪队列中,随时可以调度到CPU上

    • TIME_WAITING:调用了sleep就会进入到该状态,join(超时时间) BLOCKED:当前线程在等待锁导致了阻塞

    • WAITING:当前线程在等待唤醒

    状态转换图:

    Java多线程Thread类如何使用

    线程安全问题

    定义:操作系统中线程调度是随机的,导致程序的执行可能会出现一些bug。如果因为调度随机性引入了bug线程就是不安全的,反之则是安全的。
    解决方法:加锁,给方法直接加上synchronized关键字,此时进入方法就会自动加锁,离开方法就会自动解锁。当一个线程加锁成功的时候,其他线程尝试加锁就会触发阻塞等待,阻塞会一直持续到占用锁的线程把锁释放为止。

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

    线程不安全产生的原因:

    • 1.线程是抢占式执行,线程间的调度充满随机性。

    • 2.多个线程对同一个变量进行修改操作

    • 3.针对变量的操作不是原子的

    • 4.内存可见性也会影响线程安全(针对同一个变量t1线程循环进行多次读操作,t2线程少次修改操作,t1就不会从内存读数据了而是从寄存器里读)

    • 5.指令重排序,也是编译器优化的一种操作,保证逻辑不变的情况下调整顺序,解决方法synchronized。

    内存可见性解决方法:

    • 1.使用synchronized关键字 使用synchronized不光能保证指令的原子性,同时也能保证内存的可见性。被synchronized包裹起来的代码编译器就不会从寄存器里读。

    • 2.使用volatile关键字 能够保证内存可见性,禁止编译器作出上述优化,编译器每次执行判定相等都会重新从内存读取。

    synchronized用法

    在java中每个类都是继承自Object,每个new出来的实例里面一方面包含自己安排的属性,另一方面包含了“对象头”即对象的一些元数据。加锁操作就是在这个对象头里面设置一个标志位。

    1.直接修饰普通的方法

    使用synchronized的时候本质上是对某个“对象”进行加锁,此时的锁对象就是this。加锁操作就是在设置this的对象头的标志位,当两个线程同时尝试对同一个对象加锁的时候才有竞争,如果是两个线程在针对两个不同对象加锁就没有竞争。

    class Counter{
    	public int count;
    	synchronized public void increase(){
    	count++;
    	}
    }

    2.修饰一个代码块

    需要显示制定针对那个对象加锁(java中的任意对象都可以作为锁对象)

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

    3.修饰一个静态方法

    相当于针对当前类的类对象加锁,类对象就是运行程序的时候。class文件被加载到JVM内存中的模样。

    synchronized public static void func(){
    }

    或者

    public static void func(){
        synchronized(Counter.class){
    
        }
    }

    监视器锁monitor lock

    可重入锁就是同一个线程针对同一个锁,连续加锁两次,如果出现死锁就是不可重入锁,如果不会死锁就是可重入的。因此就把synchronized实现为可重入锁,下面的例子里啊连续加锁操作不会导致死锁。可重入锁内部会记录所被哪个线程占用也会记录加锁次数,因此后续再加锁就不是真的加锁而是单纯地把技术给自增。

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

    死锁的其他场景

    • 1.一个线程一把锁

    • 2.两个线程两把锁

    • 3.N个线程M把锁(哲学家就餐问题,解决方法:先拿编号小的筷子)

    死锁的四个必要条件(前三个都是锁本身的特点)

    • 1.互斥使用,一个锁被另一个线程占用后其他线程就用不了(锁的本质,保证原子性)

    • 2.不可抢占,一个锁被一个线程占用后其他线程不可把这个锁给挖走

    • 3.请求和保持,当一个线程占据了多把锁之后,除非显示的释放否则这些锁中都是该线程持有的

    • 4.环路等待,等待关系成环(解决:遵循固定的顺序加锁就不会出现环路等待)

    java线程类:

    • 不安全的:ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StringBuilder

    • 安全的:Vector,HashTable,ConcurrentHashMap,StringBuffer,String

    volatile

    禁止编译器优化保证内存可见性,产生原因:计算机想执行一些计算就需要把内存的数据读到CPU寄存器中,然后再从寄存器中计算写回到内存中,因为CPU访问寄存器的速度比访问内存快很多,当CPU连续多次访问内存结果都一样,CPU就会选择访问寄存器。

    JMM(Java Memory Model)Java内存模型

    就是把硬件结构在java中用专业的术语又重新抽象封装了一遍。

    工作内存(work memory)其实指的不是内存,而是CPU寄存器。
    主内存(main memeory)这才是主内存。
    原因:java作为一个跨平台编程语言要把硬件细节封装起来,假设某个计算机没有CPU或者内存同样可以套到上述模型中。

    寄存器,缓存和内存之间的关系

    CPU从内存取数据太慢,因此把数据直接放到寄存器里来读,但寄存器空间太紧张于是又搞了一个存储空间,比寄存器大比内存小速度比寄存器慢比内存快称为缓存。寄存器和缓存统称为工作内存。

    寄存器,缓存和内存之间的关系图

    Java多线程Thread类如何使用

    • 存储空间:CPU

    • 速度:CPU>L1>L2>L3>内存

    • 成本:CPU>L1>L2>L3>内存

    volatile和synchronized的区别

    • volatile只是保证可见性不保证原子性,只是处理一个线程读和一个线程写的过程。

    • synchronized都能处理

    wait和notify

    等待和通知处理线程调度随机性问题的,join也是一种控制顺序的方式更倾向于控制线程结束。wait和notify都是Object对象的方法,调用wait方法的线程就会陷入阻塞,阻塞到有线程通过notify来通知。

    public class Demo9 {
        public static void main(String[] args) throws InterruptedException {
            Object object = new Object();
            System.out.println("wait前");
            object.wait();
            System.out.println("wait后");
        }
    }

    wait内部会做三件事;

    • 1.先释放锁

    • 2.等待其他线程的通知

    • 3.收到通知后重新获得锁并继续往下执行

    因此想用wait/notify就得搭配synchronized

    public class Demo9 {
        public static void main(String[] args) throws InterruptedException {
            Object object = new Object();
            synchronized (object){
                System.out.println("wait前");
                object.wait();
                System.out.println("wait后");
            }
        }
    }

    注意:wait notify都是针对同一对象来操作的,例如现在有一个对象o,有10个线程都调用了o.wait,此时10个线程都是阻塞状态。如果调用了o.notify就会把10个线程中的一个线程唤醒。而notifyAll就会把所有10个线程全都给唤醒,此时就会竞争锁。

    本文内容来源于互联网,如有侵权请联系删除。
    作者最新文章
    编程开发
    相关文章 更多
    谷歌浏览器Mac版入口
    谷歌浏览器Mac版入口

    谷歌浏览器Mac版官方安装指南 谷歌浏览器Mac版官方安装入口是https://www.google.com/chrome/,需macOS 12+系统、500MB空间,下载.dmg后拖入应用程序安装,支持多设备同步、性能优化与隐私保护功能。 苹果电脑Chrome的安装入口究竟在哪里?这个问题最近可是

    Chrome浏览器JS脚本不运行怎么办
    Chrome浏览器JS脚本不运行怎么办

    Chrome中JavaScript未执行需依次检查:一、移除站点级禁用并添加允许域名;二、开启全局JavaScript开关;三、禁用干扰扩展;四、在开发者工具中启用JavaScript;五、重置内容设置为默认。 有时在Chrome里打开网页,会发现交互按钮点了没反应,数据加载不出来,页面仿佛“静止”

    IE浏览器怀旧版在线网址
    IE浏览器怀旧版在线网址

    IE浏览器怀旧版在线网址:一次精准的技术时光回溯 最近,不少老用户和怀旧爱好者在反复搜索一个问题:那个经典的Internet Explorer,如今还能在哪里原汁原味地体验到?答案指向一个特定的地址:https://ie.microsoft.com/legacy/。 这个网站远不止是一个简单的“皮肤

    火狐浏览器有哪些设置功能
    火狐浏览器有哪些设置功能

    火狐浏览器五大核心设置功能:解锁高效、安全与个性化体验 火狐浏览器功能强大,但如果不仔细挖掘,很多能大幅提升效率和安全性的设置可能就“藏着掖着”了。这就好比拥有一台高性能设备,却只用了基础模式。那么,如何把它调整到最顺手、最安全的状态?接下来,我们就聚焦于当前版本(截至2025年末)最关键的五大设置

    chrome搜索免验证入口
    chrome搜索免验证入口

    Chrome官方免验证入口为https://www.google.cn/chrome/,提供全平台安装包、免登录即用、本地化安全机制及引擎级性能优化。 到底该去哪里找正版、免费且无需繁琐验证的Chrome浏览器入口?这个问题困扰了不少网友。今天,我们就来直通核心,为大家详细拆解Chrome引擎的官方

    java heap space 选型思路:使用场景与区别整理
    java heap space 选型思路:使用场景与区别整理

    Java堆是JVM存储对象的核心内存区域,配置需结合场景:单体应用适中设置;大数据处理需大堆并关注GC停顿;微服务强调快速启动;高并发需精细划分堆区域。关键参数-Xms和-Xmx建议等值以稳定性能。垃圾回收器选择影响效率,如G1适用于大堆,ZGC可实现低停顿。内存错误时需监控堆状态。

    java heap space 使用中遇到的问题怎么解决
    java heap space 使用中遇到的问题怎么解决

    Java堆内存溢出错误通常因内存泄漏、数据处理需求过大或JVM参数配置不当引起。排查时可借助jmap、堆转储及MAT等工具定位问题。解决方案包括调整JVM内存参数(如-Xmx)、修复代码中的内存泄漏、优化大数据处理逻辑,并建立持续监控与预防机制,以保障应用稳定运行。

    java xml 选型思路:使用场景与区别整理
    java xml 选型思路:使用场景与区别整理

    XML在Java开发中用于配置、数据交换等场景。解析方式主要有DOM、SAX、StAX及第三方库。DOM适合操作小文件,SAX/StAX适合处理大文件流,JAXB用于对象与XML映射。选型需结合数据大小、内存、性能及团队熟悉度,现代框架常封装底层解析。

    using namespace 使用中遇到的问题怎么解决
    using namespace 使用中遇到的问题怎么解决

    命名空间的基本概念与常见引入问题在C++等编程语言中,命名空间(namespace)是一种将代码标识符(如变量、函数、类名)封装在特定名称下的机制,其主要目的是避免命名冲突,尤其是在大型项目或使用多个第三方库时。使用“using namespace”指令可以将指定命名空间中的所有名称引入当前作用域,

    c语言函数递归 实操经验总结:这些技巧很实用
    c语言函数递归 实操经验总结:这些技巧很实用

    理解递归的基本原理在C语言中,递归是一种函数调用自身的编程技术。要掌握它,首先需要理解其核心思想:将一个复杂的大问题,分解为一个或几个与原问题相似但规模更小的子问题,直到子问题足够简单,可以直接求解。这个过程通常包含两个关键部分:递归出口和递归体。递归出口定义了问题何时不再继续分解,即最简单、可直接

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    macOS软件
    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    Mac软件 更多
    灵活计算器
    灵活计算器
    macOS/iOS/Android

    灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

    赤友清理大师
    赤友清理大师
    macOS

    赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

    极度公式
    极度公式
    Windows/macOS/Linux

    极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

    WINDOWS 更多
    Windows 10
    Windows 10
    Windows

    Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

    极度公式
    极度公式
    Windows/macOS/Linux

    极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

    密码键盘
    密码键盘
    Windows/macOS/iOS/Android

    密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。