一文详细聊聊java的多线程

 更新时间:2026年01月19日 09:38:04   作者:晔子yy  
多线程就是把操作系统中的这种并发执行机制原理运用在一个程序中,把一个程序划分为若干个子任务,多个子任务并发执行,每一个任务就是一个线程,这篇文章主要介绍了java多线程的相关资料,需要的朋友可以参考下

1.什么是多线程

定义多线程是指在一个程序中同时执行多个线程的技术。每个线程代表一个独立的执行路径,但共享相同的内存空间系统资源

优势:

  • 提高CPU利用率​ :当一个线程等待I/O操作时,另一个线程可以继续执行

  • 改善响应性​ :用户界面保持响应,后台处理任务

  • 并发处理任务​ : 同时处理多个请求或计算

平时我们使用的大多都是单线程也就是main线程,虽然已经可以完成大部分的工作,但是如果任务量一旦多起来,那么你程序的吞吐量或许会指数型下降。这时候,能多点“帮手”一起完成任务就是至关重要的了,接下来我们来说下如何创建多线程

2.多线程的常见实现方式

2.1 继承Thread类

这是最经典也是最简单的实现方式,只需要自定义类继承java.lang.Thread类,重写其run()方法,run()方法中定义了线程执行的具体任务。创建该类的实例后,通过调用start()方法启动线程。

class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的代码
    }
}

// 使用
MyThread thread1 = new MyThread();
thread1.start();  // 启动线程

使用Thread虽然简单,但是有个非常显而易见的缺点:由于java只支持单继承,所以MyThread这个类不能再继承其他的父类。

2.2实现Runnable接口

实现Runnable接口也可以创建多线程,并且没有继承Thread类的缺点,这也是开发中推荐使用的多线程实现的方式之一

class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的代码

    }
}

// 使用
Thread thread2 = new Thread(new MyRunnable());
thread2.start();

2.3实现Callable接口与Future

java.util.concurrent.Callable接口类似于Runnable,不同点在于Callable的call()方法可以有返回值并且可以抛出异常。要执行Callable任务,需将它包装进一个FutureTask,因为Thread类的构造器只接受Runnable参数,而FutureTask实现了Runnable接口。

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // 线程执行的代码,这里返回一个整型结果
        return 1;
    }
}

public static void main(String[] args) {
    MyCallable task = new MyCallable();

    //使用FutureTask包装
    FutureTask<Integer> futureTask = new FutureTask<>(task);
    Thread t = new Thread(futureTask);
    
    t.start();

    try {
        Integer result = futureTask.get(); // 获取线程执行结果

        System.out.println("Result: " + result);
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

可以看到,使用这种方法编程稍微有些复杂,所以我更推荐平时使用第二种方式去开启线程

3.线程的并发安全问题

3.1问题抛出

在jvm的内存结构中,多线程之间有一块共享的区域是堆内存,该区域经常存放对象、数组等,简单来说,平时new出来的对象大部分都放在了堆内存中,此时如果我们随意地使用多线程就会引发一个严重的问题——线程并发安全问题。

举个简单的例子:有100张票,3个线程去分,结果会如何?

//开启三个线程
Thread thread1 = new Mythread();
Thread thread2 = new Mythread();
Thread thread3 = new Mythread();

thread1.start();
thread2.start();
thread3.start();

//主线程休眠1秒
Thread.sleep(1000);
System.out.println("执行了"+ticket.count+"次");

我们可以清楚的观察到不仅票的数量不是递减的,总执行的次数也对不上,这就是多线程环境下的并发安全问题。

3.2解析问题

问题的根本就是ticket是全局共享的,对于对象成员变量的修改,线程会先拿到值后再做修改,由于这两步并不是同时进行的,所以会导致在一个线程做修改之前,另一个线程拿到了修改前的值,比如t1先取100,在做-1之前,t2也取到了100,两个线程先后做-1操作,此时t3来拿值,取到了98,不仅少了99这个状态,票数100也出现了两次,在打印的时候,由于各个线程顺序的不确定性,也会出现后打印的票数比前打印的票数多的情况。

那怎么解决问题呢?

3.3 synchronized关键字

定义:Java语言的关键字,用于实现线程同步。当修饰方法或代码块时,同一时间仅允许一个线程执行该同步区域,其他线程需等待当前线程释放锁。

synchronized就像是一把锁,当一个线程想要执行被synchronized修饰的方法时,就必须先拿到锁才能执行,之后又有线程想执行该方法后就会被阻塞,直到锁被释放才能去竞争锁,竞争成功后就可以执行方法

synchronized有三种实现方法:

1.同步实例方法

public class BankAccount {
    private int balance = 1000;
    
    // 锁住当前账户对象(this)
    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }
    
}

由于锁的是同步方法,所以实际上是对这个对象(this)进行了上锁,这就导致只有在多线程一起使用这个对象的时候才可以实现数据隔离,如果又new了一个BankAccount对象,这时候就不能实现隔离,举个简单的例子,把这个对象当作是一间房子,锁同步方法仅仅只能防止别人进你家,不能防止别人进其他人家。

2.同步静态方法

public class BankAccount {
    private int balance = 1000;
    
    // 锁住当前账户对象(this)
    public static synchronized void withdraw(int amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }
    
}

static修饰后的方法就是静态方法,生命周期上升到类的级别,与类强绑定,这时候使用synchronized修饰后相当于锁住了整个类,由于类是全局唯一的,所以就解决了创建多对象后无法实现数据隔离的情况了,再拿刚刚例子来说,这次别人既进不来你家,也进不去其他的房子里。万事大吉了!

3.同步代码块

public class BankAccount {
    private int balance = 1000;
     private final Object lock = new Object();  // 专门的锁对象
    
    // 锁住当前账户对象(this)
    public  void withdraw(int amount) {
       synchronized(lock) { // 使用专门的锁对象
        if (balance >= amount) {
            balance -= amount;
        }
      }
    }
    
}
public class BankAccount {
    private int balance = 1000;
    
    // 锁住当前账户对象(this)
    public  void withdraw(int amount) {
       synchronized(Object.class) { // 使用全局的类上锁
        if (balance >= amount) {
            balance -= amount;
        }
      }
    }
    
}

相比于前两种,这一种方法可以做到锁的粒度更细,性能会有所提升,在synchronized()中,你既可以模拟第一种方法锁住对象,也可以模拟第二种方法使用类去全局上锁,两者效果均不变。

3.4解决问题

好了,我们已经大概了解了synchronized关键字的使用,接下来就是解决遗留的问题了,方法很简单,直接在buyTicket()前使用synchronized关键字修饰一下即可。

  public synchronized static void buyTicket() {
            ticketid--;
            System.out.println(Thread.currentThread().getName() + "买了票,现在还剩下" + ticketid + "张");
            count++;
    }

加上sychronized后我们再来查看结果

现在无论我们执行多少次,结果都不会出问题了。

总结

到此这篇关于java多线程的文章就介绍到这了,更多相关java多线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • shiro+jwt登陆验证实现教程

    shiro+jwt登陆验证实现教程

    本文介绍了如何使用Shiro和JWT实现登录认证,并通过自定义Realm和过滤器来完成身份验证和授权,通过测试,展示了如何携带JWT令牌进行接口访问,并验证了其有效期
    2026-03-03
  • Spring Boot 项目集成 Redisson 实现延迟队列的详细过程

    Spring Boot 项目集成 Redisson 实现延迟队列的详细过程

    本文介绍延迟队列在订单超时等场景的应用及四种技术方案对比,推荐Redisson延迟队列,提供项目结构与测试源码,对Spring Boot  Redisson延迟队列相关知识感兴趣的朋友一起看看吧
    2025-06-06
  • Java中java.lang.ClassCastException异常原因以及解决方法详解

    Java中java.lang.ClassCastException异常原因以及解决方法详解

    这篇文章主要给大家介绍了关于Java中java.lang.ClassCastException异常原因以及解决方法的相关资料,ClassCastException从字面上看是类型转换错误,通常是进行强制类型转换时候出的错误,需要的朋友可以参考下
    2024-02-02
  • 基于Java实现Json文件转换为Excel文件

    基于Java实现Json文件转换为Excel文件

    这篇文章主要为大家详细介绍了如何利用Java实现Json文件转换为Excel文件,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以参考一下
    2022-12-12
  • java设计模式责任链模式原理案例详解

    java设计模式责任链模式原理案例详解

    一个事件需要经过多个对象处理是一个挺常见的场景,譬如采购审批流程,请假流程,软件开发中的异常处理流程,web请求处理流程等各种各样的流程,可以考虑使用责任链模式来实现
    2021-09-09
  • Java垃圾回收机制的finalize方法实例分析

    Java垃圾回收机制的finalize方法实例分析

    这篇文章主要介绍了Java垃圾回收机制的finalize方法,结合实例形式分析了finalize方法的特点及在垃圾回收机制中的相关操作技巧,需要的朋友可以参考下
    2019-08-08
  • JAVA匿名内部类(Anonymous Classes)的具体使用

    JAVA匿名内部类(Anonymous Classes)的具体使用

    本文主要介绍了JAVA匿名内部类,匿名内部类在我们JAVA程序员的日常工作中经常要用到,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • 如何利用IDEA搭建SpringBoot项目整合mybatis实现简单的登录功能

    如何利用IDEA搭建SpringBoot项目整合mybatis实现简单的登录功能

    这篇文章主要介绍了如何利用IDEA搭建SpringBoot项目整合mybatis实现简单的登录功能,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • Spring Boot引入swagger-ui 后swagger-ui.html无法访问404的问题

    Spring Boot引入swagger-ui 后swagger-ui.html无法访问404的问题

    这篇文章主要介绍了Spring Boot引入swagger-ui 后swagger-ui.html无法访问404的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • mybatis自动生成@Table、@Column、@Id注解的方法

    mybatis自动生成@Table、@Column、@Id注解的方法

    这篇文章主要介绍了mybatis自动生成@Table、@Column、@Id注解的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12

最新评论