当前位置:

首页 > 编程开发 > 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个线程全都给唤醒,此时就会竞争锁。

      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发 Java thread
      相关文章 更多
      Java单元测试入门:使用JUnit 5编写第一个测试用例
      Java单元测试入门:使用JUnit 5编写第一个测试用例

      面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

      Java变量未初始化报错解析:局部变量与成员变量默认值差异
      Java变量未初始化报错解析:局部变量与成员变量默认值差异

      深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

      Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
      Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

      详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

      Java中final与static final常量的定义及初始化规则
      Java中final与static final常量的定义及初始化规则

      深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

      Java抽象方法与接口方法的区别及选型建议
      Java抽象方法与接口方法的区别及选型建议

      深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

      Java实现HTTP请求与JSON解析的完整代码示例
      Java实现HTTP请求与JSON解析的完整代码示例

      本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

      Java递归出现StackOverflowError的原因分析及栈空间优化方案
      Java递归出现StackOverflowError的原因分析及栈空间优化方案

      详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

      Java空指针异常(NPE)的代码排查与修复实例
      Java空指针异常(NPE)的代码排查与修复实例

      针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

      selenium上传文件教程本地文件路径设置方法
      selenium上传文件教程本地文件路径设置方法

      深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

      大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
      大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

      针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

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

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

      Windows
      Windows

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

      PDF教程
      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      Mac软件 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      Aperty macOS版
      Aperty macOS版
      macOS

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      WINDOWS 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      PixTV 网页版
      PixTV 网页版
      Windows

      PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。