Java多线程详解,一篇文章读懂多线程。

本文详细介绍了Java多线程的基本概念、创建与启动、线程调度、生命周期、线程同步和通信,以及线程安全问题的解决方案。通过实例分析了继承Thread类、实现Runnable接口、Callable接口和线程池创建线程的不同方式,并探讨了多线程的优缺点和适用场景。此外,还讲解了Java线程调度算法、死锁问题以及线程间的通信机制。
摘要由CSDN通过智能技术生成

1. 基本概念

  • 程序(program)

    程序是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码(还没有运行起来),静态对象。

  • 进程(process)

    进程是程序的一次执行过程,也就是说程序运行起来了,加载到了内存中,并占用了cpu的资源。这是一个动态的过程:有自身的产生、存在和消亡的过程,这也是进程的生命周期。

    进程是系统资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。

  • 线程(thread)

    进程可进一步细化为线程,是一个程序内部的执行路径。

    若一个进程同一时间并行执行多个线程,那么这个进程就是支持多线程的。

    线程是cpu调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。

    一个进程中的多个线程共享相同的内存单元/内存地址空间——》他们从同一堆中分配对象,可以访问相同的变量和对象。这就使得相乘间通信更简便、搞笑。但索格线程操作共享的系统资源可能就会带来安全隐患(隐患为到底哪个线程操作这个数据,可能一个线程正在操作这个数据,有一个线程也来操作了这个数据v)。

    • 配合JVM内存结构了解(只做了解即可)

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wJraFAzW-1619229368626)(https://i.vgy.me/karuEk.png)]

      class文件会通过类加载器加载到内存空间。

      其中内存区域中每个线程都会有虚拟机栈和程序计数器。

      每个进程都会有一个方法区和堆,多个线程共享同一进程下的方法区和堆。

  • CPU单核和多核的理解

    单核的CPU是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。同时间段内有多个线程需要CPU去运行时,CPU也只能交替去执行多个线程中的一个线程,但是由于其执行速度特别快,因此感觉不出来。

    多核的CPU才能更好的发挥多线程的效率。

    对于Java应用程序java.exe来讲,至少会存在三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。如过发生异常时会影响主线程。

  • Java线程的分类:用户线程 和 守护线程

    • Java的gc()垃圾回收线程就是一个守护线程
    • 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以吧一个用户线程变成一个守护线程。
  • 并行和并发

    • 并行:多个cpu同时执行多个任务。比如,多个人做不同的事。
    • 并发:一个cpu(采用时间片)同时执行多个任务。比如,渺少、多个人做同一件事。
  • 多线程的优点

    1. 提高应用程序的响应。堆图像化界面更有意义,可以增强用户体验。
    2. 提高计算机系CPU的利用率。
    3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
  • 何时需要多线程

    • 程序需要同时执行两个或多个任务。
    • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
    • 需要一些后台运行的程序时。

2. 线程的创建和启动

2.1. 多线程实现的原理

  • Java语言的JVM允许程序运行多个线程,多线程可以通过Java中的java.lang.Thread类来体现。
  • Thread类的特性
    • 每个线程都是通过某个特定的Thread对象的run()方法来完成操作的,经常吧run()方法的主体称为线程体。
    • 通过Thread方法的start()方法来启动这个线程,而非直接调用run()。

2.2.多线程的创建,方式一:继承于Thread类

  1. 创建一个继承于Thread类的子类。
  2. 重写Thread类的run()方法。
  3. 创建Thread类的子类的对象。
  4. 通过此对象调用start()来启动一个线程。

**代码实现:**多线程执行同一段代码

package com.broky.multiThread;

/**
 * @author 13roky
 * @date 2021-04-19 21:22
 */
public class ThreadTest extends Thread{
   
    @Override
    //线程体,启动线程时会运行run()方法中的代码
    public void run() {
   
        //输出100以内的偶数
        for (int i = 0; i < 100; i++) {
   
            if (i % 2 == 0){
   
                System.out.println(Thread.currentThread().getName()+":\t"+i);
            }
        }
    }

    public static void main(String[] args) {
   
        //创建一个Thread类的子类对象
        ThreadTest t1 = new ThreadTest();
        //通过此对象调用start()启动一个线程
        t1.start();
        //注意:已经启动过一次的线程无法再次启动
        //再创建一个线程
        ThreadTest t2 = new ThreadTest();
        t2.start();

        //另一种调用方法,此方法并没有给对象命名
        new ThreadTest().start();

        System.out.println("主线程");
    }
}

多线程代码运行图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PguN728e-1619229368627)(https://i.vgy.me/at2IMI.png)]

多线程执行多段代码

package com.broky.multiThread.exer;

/**
 * @author 13roky
 * @date 2021-04-19 22:43
 */
public class ThreadExerDemo01 {
   
    public static void main(String[] args) {
   
        new Thread01().start();
        new Thread02().start();
    }
}

class Thread01 extends Thread {
   
    @Override
    public void run() {
   
        for (int i = 0; i < 100; i++) {
   
            if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
        }
    }
}

class Thread02 extends Thread {
   
    @Override
    public void run() {
   
        for (int i = 0; i < 100; i++) {
   
            if (i % 2 != 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
        }
    }
}

2.3.多线程的创建,方式一:创建Thread匿名子类(也属于方法一)

package com.broky.multiThread;

/**
 * @author 13roky
 * @date 2021-04-19 22:53
 */
public class AnonymousSubClass {
   
    public static void main(String[] args) {
   

        new Thread(){
   
            @Override
            public void run() {
   
                for (int i = 0; i < 100; i++) {
   
                    if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
                }
            }
        }.start();

    }
}

2.4. 多线程的创建,方式二:实现Runnable接口

  1. 创建一个实现Runnable接口的类。
  2. 实现类去实现Runnable接口中的抽象方法:run()。
  3. 创建实现类的对象。
  4. 将此对象作为参数传到Thread类的构造器中,创建Thread类的对象。
  5. 通过Thread类的对象调用start()方法。
package com.broky.multiThread;

/**
 * @author 13roky
 * @date 2021-04-20 23:16
 */
public class RunnableThread {
   
    public static void main(String[] args) {
   
        //创建实现类的对象
        RunnableThread01 runnableThread01 = new RunnableThread01();
        //创建Thread类的对象,并将实现类的对象当做参数传入构造器
        Thread t1 = new Thread(runnableThread01);
        //使用Thread类的对象去调用Thread类的start()方法:①启动了线程 ②Thread中的run()调用了Runnable中的run()
        t1.start();

        //在创建一个线程时,只需要new一个Thread类就可,不需要new实现类
        Thread t2 = new Thread(runnableThread01);
        t2.start();
    }
}

//RunnableThread01实现Runnable接口的run()抽象方法
class RunnableThread01 implements Runnable {
   
    @Override
    public void run() {
   
        for (int i = 0; i < 100; i++) {
   
            if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
        }
    }
}

2.4.1. 比较创建线程的两种方式

  • Java中只允许单进程,以卖票程序TiketSales类来说,很有可能这个类本来就有父类,这样一来就不可以继承Thread类来完成多线程了,但是一个类可以实现多个接口,因此实现的方式没有类的单继承性的局限性,用实现Runnable接口的方式来完成多线程更加实用。
  • 实现Runnable接口的方式天然具有共享数据的特性(不用static变量)。因为继承Thread的实现方式,需要创建多个子类的对象来进行多线程,如果子类中有变量A,而不使用static约束变量的话,每个子类的对象都会有自己独立的变量A,只有static约束A后,子类的对象才共享变量A。而实现Runnable接口的方式,只需要创建一个实现类的对象,要将这个对象传入Thread类并创建多个Thread类的对象来完成多线程,而这多个Thread类对象实际上就是调用一个实现类对象而已。实现的方式更适合来处理多个线程有共享数据的情况。
  • 联系:Thread类中也实现了Runnable接口
  • 相同点两种方式都需要重写run()方法,线程的执行逻辑都在run()方法中

2.5. 多线程的创建,方式三:实现Callable接口

与Runnable相比,Callable功能更强大

  1. 相比run()方法,可以有返回值
  2. 方法可以抛出异常
  3. 支持泛型的返回值
  4. 需要借助FutureTask类,比如获取返回结果
package com.broky.multiThread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 创建线程的方式三:实现Callable接口。 ---JDK5新特性
 * 如何理解Callable比Runnable强大?
 * 1.call()可以有返回值
 * 2.call()可以抛出异常被外面的操作捕获
 * @author 13roky
 * @date 2021-04-22 21:04
 */

//1.创建一个实现Callable的实现类
class NumThread implements Callable<Integer>{
   
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Integer call() throws Exception {
   
        int sum = 0;
        for (int i = 1; i < 100; i++) {
   
            if(i%2==0){
   
                System.out.println(i)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值